为什么在Go语言中不能将一个类型的切片替换为另一个类型的切片?

huangapple go评论74阅读模式
英文:

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 woofRunnerss 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 appending 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 woofers, a []woofRunner is just as good as a []woofer),
  • writing to it (for writing woofRunners, 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)
}

huangapple
  • 本文由 发表于 2017年6月17日 17:07:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/44602741.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定