英文:
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{}
的情况。切片/数组中的每个指针都指向可以直接转换为另一种类型的类型。出于相同的原因,使用这些指针应该是可能的,就像将dog[0]
传递给oneWoof
一样。)
注意1:我知道一种解决方法是循环遍历并逐个转换项目。我在这里的问题是为什么需要这样做,是否有更好的解决方法。
注意2:关于可赋值性的规则:
如果值x可以赋值给类型为T的变量,那么T是一个接口类型并且x实现了T。
我们不能说如果切片/数组的类型可以赋值给另一种类型,那么这些类型的数组也可以赋值吗?
英文:
I'm trying to understand Go's type conversion rules. Say we have these interfaces:
type woofer interface {
woof()
}
type runner interface {
run()
}
type woofRunner interface {
woofer
runner
}
and to satisfy the interfaces we have a dog
type:
type dog struct{}
func (*dog) run() {}
func (*dog) woof() {}
These two functions are using the interfaces:
func allWoof(ws []woofer) {}
func oneWoof(w woofer) {}
To use these methods I can write the following:
dogs := make([]woofRunner, 10)
oneWoof(dogs[0])
allWoof(dogs)
The first function oneWoof()
works as expected; a *dog
implements all oneWoof
needs, which is a woof
function.
However for the second function allWoof
, Go won't compile the attempted invocation, reporting the following:
> cannot use dogs (type []woofRunner) as type []woofer in argument to allWoof
Using a type conversion is also impossible; writing []woofer(dogs)
fails as well:
> cannot convert dogs (type []woofRunner) to type []woofer
Every member of []woofRunner
has all the necessary functions to satisfy a []woofer
, so why is this conversion prohibited?
(I'm not sure if this is the same case explained in the Go FAQ and in various questions on Stack Overflow in which people ask about converting type T
to interface{}
. Every pointer in the slice/array is pointing to a type that is directly convertible to another type. Using these pointers should be possible for the same reason that passing dog[0]
to 'oneWoof` is possible.)
Note 1: I know one solution is to loop over and and convert the items one by one. My question here is why that's necessary and whether there is a better solution.
Note 2: Regarding the rules of Assignability:
> A value x is assignable to a variable of type T [when] T is an interface type and x implements T.
Can't we say if the type of the slice/array is assignable to another type, then arrays of those types are also assignable?
答案1
得分: 4
除了Go语言在其他答案中提到的拒绝在这些变异关系中转换切片的问题,思考一下Go为什么拒绝这样做也是有用的,即使在内存表示上两种类型是相同的。
在你的例子中,将woofRunners
的切片作为[]woofer
类型的参数提供,是要求对切片的元素类型进行_协变_处理。当从切片中读取时,确实,由于woofRunner
是 woofer
,你知道在[]woofRunner
中的每个元素都满足寻找[]woofer
的读取器。
然而,在Go中,切片是引用类型。当将切片作为参数传递给函数时,切片会被复制,但在调用的函数体中使用的副本仍然引用相同的后备数组(在append
超出其容量之前不需要重新分配)。数组的可变视图——更一般地说,向集合中插入项——需要对元素类型进行_逆变_处理。也就是说,当要求一个函数参数以插入或覆盖woofRunner
类型的元素时,提供[]woofer
是可以接受的。
问题是函数是否要求切片参数用于:
- 从中读取(对于读取
woofer
,[]woofRunner
和[]woofer
一样好), - 写入(对于写入
woofRunner
,[]woofer
和[]woofRunner
一样好), - 或者两者都是(两者都不能替代对方)。
想象一下,如果Go以协变方式接受切片参数,然后有人来改变allWoof
的代码如下:
// 另一种满足`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) // 不能编译,但如果可以呢?
即使Go愿意将[]*dog
视为[]woofer
,我们最终会在这里的*dog
数组中得到一个*wolf
。一些语言通过对尝试的数组插入或覆盖进行运行时类型检查来防止这样的意外,但由于Go甚至不允许我们走到这一步,它不需要这些额外的检查。
英文:
In addition to Go's refusal to convert slices along these variance relationships addressed in other answers here, it's useful to think through why Go refuses to do so, even when the in-memory representation would be the same between the two types.
In your example, supplying a slice of woofRunners
s as a parameter of type []woofer
is asking for covariant treatment of the slice's element type. When reading from the slice, indeed, since a woofRunner
is a woofer
, you know that every element present in a []woofRunner
will satisfy a reader looking for []woofer
.
However, in Go, a slice is a reference type. When passing a slice as an argument to a function, the slice is copied, but the copy used in the invoked function body continues to refer to the same backing array (absent reallocation necessary before append
ing beyond its capacity). The mutable view of an array—more generally, inserting an item into a collection—requires contravariant treatment of the element type. That is, when it comes to demanding a function parameter with the intention of inserting into or overwriting an element of type woofRunner
, it's acceptable to supply a []woofer
.
The question is whether the function is demanding the slice parameter for
- reading from it (for reading
woofer
s, a[]woofRunner
is just as good as a[]woofer
), - writing to it (for writing
woofRunner
s, a[]woofer
is just as good as a[]woofRunner
), - or both (neither is an acceptable substitute for the other).
Consider what would happen if Go did accept slice parameters in covariant fashion, and someone came along and changed allWoof
as follows:
// 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?
Even if Go was willing to treat a []*dog
as a []woofer
, we would wind up with a *wolf
in our array of *dog
here. Some languages defend against such an accident with run-time type checks on the attempted array insertion or overwrite, but because Go precludes us from even making it this far, it doesn't need these additional checks.
答案2
得分: 2
从Go参考中可以得知:
如果两个切片类型具有相同的元素类型,则它们是相同的。
以及
如果两个接口类型具有相同的方法集,方法集中的方法具有相同的名称和相同的函数类型,那么它们是相同的。
所以woofRunner
不等同于woofer
,这导致[]woofRunner
不等同于[]woofer
。
英文:
From Go reference
> Two slice types are identical if they have identical element types.
and
> Two interface types are identical if they have the same set of methods with the same names and identical function types(...).
So woofRunner
isn't identical to woofer
and this leads us that []woofRunner
isn't identical to []woofer
.
答案3
得分: 0
你必须在循环中将接口数组转换为具体类型的数组:
var woofers []woofer
for _, w := range dogs {
woofers = append(woofers, w)
}
英文:
You must convert arrays of interfaces in a loop:
var woofers []woofer
for _, w := range dogs {
woofers = append(woofers, w)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论