为什么我不能在 Go 中用一种类型的切片替换另一种类型?
Why can't I substitute a slice of one type for another in Go?
我正在尝试了解 Go 的类型转换规则。假设我们有这些接口:
type woofer interface {
woof()
}
type runner interface {
run()
}
type woofRunner interface {
woofer
runner
}
为了满足接口,我们有一个 dog
类型:
type dog struct{}
func (*dog) run() {}
func (*dog) woof() {}
这两个函数正在使用接口:
func allWoof(ws []woofer) {}
func oneWoof(w woofer) {}
要使用这些方法,我可以编写以下内容:
dogs := make([]woofRunner, 10)
oneWoof(dogs[0])
allWoof(dogs)
第一个函数 oneWoof()
按预期运行; *dog
实现所有 oneWoof
需求,这是一个 woof
功能。
但是对于第二个函数 allWoof
,Go 不会编译尝试的调用,报告如下:
cannot use dogs (type []woofRunner) as type []woofer in argument to allWoof
使用类型转换也是不可能的;写入 []woofer(dogs)
也会失败:
cannot convert dogs (type []woofRunner) to type []woofer
[]woofRunner
的每个成员都有满足[]woofer
的所有必要功能,为什么要禁止这种转换?
(我不确定这是否与 Go FAQ 和 Stack Overflow 上的各种问题中解释的情况相同,人们在这些问题中询问有关将类型 T
转换为 interface{}
。slice/array 中的每个指针都指向可直接转换为另一种类型的类型。使用这些指针应该是可能的,原因与将 dog[0]
传递给 'oneWoof` 是可能的。)
注1:我知道一种解决方案是循环并逐项转换。我的问题是为什么这是必要的以及是否有更好的解决方案。
注2:关于Assignability的规则:
A value x is assignable to a variable of type T [when] T is an interface type and x implements T.
我们不能说如果 slice/array 的类型可以赋值给另一个类型,那么这些类型的数组也可以赋值吗?
您必须循环转换接口数组:
var woofers []woofer
for _, w := range dogs {
woofers = append(woofers, w)
}
Two slice types are identical if they have identical element types.
和
Two interface types are identical if they have the same set of methods with the same names and identical function types(...).
所以 woofRunner
与 woofer
不同,这导致我们 []woofRunner
与 []woofer
不同。
除了 Go 拒绝沿此处其他答案中提到的这些方差关系转换切片之外,思考 为什么 Go 拒绝这样做是很有用的,即使在 -两种类型之间的内存表示是相同的。
在您的示例中,提供 woofRunners
的切片作为类型 []woofer
的参数要求切片的元素类型为 covariant treatment。当 从切片中读取 时,确实,因为 woofRunner
是 woofer
,你知道每个元素出现在[]woofRunner
将满足 reader 寻找 []woofer
。
然而,在 Go 中,切片是一种引用类型。当将一个切片作为参数传递给一个函数时,该切片被复制,但被调用的函数主体中使用的副本继续引用相同的支持数组(在 append
超出其容量之前不需要重新分配)。数组的可变视图——更一般地说,将一个项目插入集合中——需要对元素类型进行逆变处理。也就是说,当涉及到要求函数参数以 插入 或 覆盖 类型 woofRunner
的元素时,它是可以提供 []woofer
.
问题是函数是否要求
的切片参数
- 从中读取(对于读取
woofer
s,[]woofRunner
与 []woofer
一样好),
- 写入(对于写入
woofRunner
s,[]woofer
与 []woofRunner
一样好),
- 或两者兼而有之(两者都不是可接受的替代品)。
考虑一下如果 Go 确实以协变方式接受切片参数会发生什么,并且有人出现并更改 allWoof
如下:
// Another type satisfying `woofRunner`:
type wolf struct{}
func (*wolf) run() {}
func (*wolf) woof() {}
func allWoof(ws []woofer) {
if len(ws) > 0 {
ws[0] = &wolf{}
}
}
dogs := []*dog{&dog{}, &dog{}}
allWoof(dogs) // Doesn't compile, but what if it did?
即使 Go 愿意将 []*dog
视为 []woofer
,我们也会在此处的 *dog
数组中得到 *wolf
。一些语言通过对尝试的数组插入或覆盖进行 运行 次类型检查来防止此类事故,但是因为 Go 阻止我们做到这一点,所以它不需要这些额外的检查。
我正在尝试了解 Go 的类型转换规则。假设我们有这些接口:
type woofer interface {
woof()
}
type runner interface {
run()
}
type woofRunner interface {
woofer
runner
}
为了满足接口,我们有一个 dog
类型:
type dog struct{}
func (*dog) run() {}
func (*dog) woof() {}
这两个函数正在使用接口:
func allWoof(ws []woofer) {}
func oneWoof(w woofer) {}
要使用这些方法,我可以编写以下内容:
dogs := make([]woofRunner, 10)
oneWoof(dogs[0])
allWoof(dogs)
第一个函数 oneWoof()
按预期运行; *dog
实现所有 oneWoof
需求,这是一个 woof
功能。
但是对于第二个函数 allWoof
,Go 不会编译尝试的调用,报告如下:
cannot use dogs (type []woofRunner) as type []woofer in argument to allWoof
使用类型转换也是不可能的;写入 []woofer(dogs)
也会失败:
cannot convert dogs (type []woofRunner) to type []woofer
[]woofRunner
的每个成员都有满足[]woofer
的所有必要功能,为什么要禁止这种转换?
(我不确定这是否与 Go FAQ 和 Stack Overflow 上的各种问题中解释的情况相同,人们在这些问题中询问有关将类型 T
转换为 interface{}
。slice/array 中的每个指针都指向可直接转换为另一种类型的类型。使用这些指针应该是可能的,原因与将 dog[0]
传递给 'oneWoof` 是可能的。)
注1:我知道一种解决方案是循环并逐项转换。我的问题是为什么这是必要的以及是否有更好的解决方案。
注2:关于Assignability的规则:
A value x is assignable to a variable of type T [when] T is an interface type and x implements T.
我们不能说如果 slice/array 的类型可以赋值给另一个类型,那么这些类型的数组也可以赋值吗?
您必须循环转换接口数组:
var woofers []woofer
for _, w := range dogs {
woofers = append(woofers, w)
}
Two slice types are identical if they have identical element types.
和
Two interface types are identical if they have the same set of methods with the same names and identical function types(...).
所以 woofRunner
与 woofer
不同,这导致我们 []woofRunner
与 []woofer
不同。
除了 Go 拒绝沿此处其他答案中提到的这些方差关系转换切片之外,思考 为什么 Go 拒绝这样做是很有用的,即使在 -两种类型之间的内存表示是相同的。
在您的示例中,提供 woofRunners
的切片作为类型 []woofer
的参数要求切片的元素类型为 covariant treatment。当 从切片中读取 时,确实,因为 woofRunner
是 woofer
,你知道每个元素出现在[]woofRunner
将满足 reader 寻找 []woofer
。
然而,在 Go 中,切片是一种引用类型。当将一个切片作为参数传递给一个函数时,该切片被复制,但被调用的函数主体中使用的副本继续引用相同的支持数组(在 append
超出其容量之前不需要重新分配)。数组的可变视图——更一般地说,将一个项目插入集合中——需要对元素类型进行逆变处理。也就是说,当涉及到要求函数参数以 插入 或 覆盖 类型 woofRunner
的元素时,它是可以提供 []woofer
.
问题是函数是否要求
的切片参数- 从中读取(对于读取
woofer
s,[]woofRunner
与[]woofer
一样好), - 写入(对于写入
woofRunner
s,[]woofer
与[]woofRunner
一样好), - 或两者兼而有之(两者都不是可接受的替代品)。
考虑一下如果 Go 确实以协变方式接受切片参数会发生什么,并且有人出现并更改 allWoof
如下:
// Another type satisfying `woofRunner`:
type wolf struct{}
func (*wolf) run() {}
func (*wolf) woof() {}
func allWoof(ws []woofer) {
if len(ws) > 0 {
ws[0] = &wolf{}
}
}
dogs := []*dog{&dog{}, &dog{}}
allWoof(dogs) // Doesn't compile, but what if it did?
即使 Go 愿意将 []*dog
视为 []woofer
,我们也会在此处的 *dog
数组中得到 *wolf
。一些语言通过对尝试的数组插入或覆盖进行 运行 次类型检查来防止此类事故,但是因为 Go 阻止我们做到这一点,所以它不需要这些额外的检查。