为什么我不能在Go中将一种类型的切片替换为另一种类型?

时间:2017-06-17 09:07:27

标签: go covariance

我正在尝试了解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不会编译尝试的调用,报告以下内容:

  

不能使用dog(type [] woofRunner)作为类型[]低音扬声器参与allWoof

使用类型转换也是不可能的;写[]woofer(dogs)也失败了:

  

不能将狗(type [] woofRunner)转换为[]低音单元

[]woofRunner的每位成员都拥有满足[]woofer所需的所有功能,为什么禁止这种转换呢?

(我不确定这是否与Go FAQ中解释的相同,以及有关Stack Overflow的各种问题,其中人们会询问有关将T类型转换为interface{}的问题。切片/数组中的每个指针都指向一个可直接转换为另一种类型的类型。使用这些指针的原因与将dog[0]传递给'oneWoof`的原因相同。)

注1 :我知道一个解决方案是循环并逐个转换项目。我的问题是为什么这是必要的以及是否有更好的解决方案。

注2 :关于Assignability的规则:

  

值x可赋值给T类型的变量[when] T是接口类型,x实现T。

我们不能说切片/数组的类型是否可以分配给另一种类型,那么这些类型的数组也是可分配的吗?

3 个答案:

答案 0 :(得分:2)

来自Protection

  

如果两种切片类型具有相同的元素类型,则它们是相同的。

  

如果两个接口类型具有相同名称和相同功能类型(...)的相同方法集,则它们是相同的。

因此woofRunnerwoofer不相同,这导致我们[]woofRunner[]woofer不相同。

答案 1 :(得分:2)

除了Go拒绝在此处的其他答案中解决的这些方差关系中转换切片之外,通过为什么 Go拒绝这样做是有用的,即使内存中的表示将是两种类型之间相同。

在您的示例中,提供woofRunners s片作为类型[]woofer的参数,要求片段的元素类型为covariant treatment。确实,从切片读取时,由于woofRunner woofer,您知道[]woofRunner中存在的每个元素将满足寻找[]woofer的读者。

但是,在Go中,切片是引用类型。将切片作为参数传递给函数时,会复制切片,但调用的函数体中使用的副本将继续引用相同的后备数组(在{{1}超出其容量之前不需要重新分配)。数组的可变视图 - 更一般地说,将项插入到集合中 - 需要对元素类型进行逆变处理。也就是说,当要求函数参数意图插入覆盖类型为append的元素时,可以提供{{{ 1}}。

问题是该功能是否要求

的切片参数
  • 从中读取(对于阅读woofRunner s,[]wooferwoofer一样好,
  • 写信给它(写[]woofRunner s,[]wooferwoofRunner一样好,
  • 或两者(两者都不是可接受的替代品)。

考虑如果Go以协变方式接受切片参数会发生什么,有人出现并按如下方式更改[]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? ,我们最终会在[]*dog数组[]woofer中找到*wolf。有些语言通过对尝试的数组插入或覆盖进行运行时类型检查来抵御这种事故,但是因为Go阻止我们甚至做到这一点,所以它不需要这些额外的检查。

答案 2 :(得分:0)

您必须在循环中转换接口数组:

var woofers []woofer
for _, w := range dogs {
    woofers = append(woofers, w)
}