一尘不染

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

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

不能在allWoof的参数中将狗([] woofRunner类型)用作[] woofer类型

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

无法将狗([] woofRunner类型)转换为[] woofer类型

每个成员[]woofRunner都有满足的所有必要功能[]woofer,那么为什么禁止这种转换?

(我不知道这是一样的情况下,在去解释的常见问题,并在堆栈溢出,使人们问转换类型的各种问题Tinterface{}。片/阵列中的每个指针指向一个类型,它是直接转换为另一种类型。出于与传递dog[0]给’oneWoof`
相同的原因,应该可以使用这些指针。)

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

注2 :关于可分配性规则:

当T是接口类型且x实现T时,值x可分配给类型T的变量。

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


阅读 222

收藏
2020-07-02

共1个答案

一尘不染

除了Go拒绝根据此处其他答案中解决的这些方差关系转换切片之外,思考一下 为什么 Go拒绝这样做是有用的,即使两种类型的内存中表示形式相同。

在您的示例中,提供woofRunnerss 的切片作为类型的参数[]woofer要求对切片的元素类型进行 协变
处理
。实际上,当从切片中
读取内容时 ,由于a woofRunner 是a
woofer,因此您知道a中存在的每个元素[]woofRunner都会满足读者的需求[]woofer

但是,在Go中,切片是参考类型。当将切片作为参数传递给函数时,将复制切片,但是在调用的函数主体中使用的副本将继续引用相同的后备数组(append超出其容量之前必须进行重新分配)。数组的可变视图(通常是将项目插入集合)需要对元素类型进行
逆向 处理。也就是说,当需要 插入覆盖
type元素的目的是要求函数参数时woofRunner,提供a是可以接受的[]woofer

问题是函数是否要求使用slice参数

  • 从中读取(对于woofers来说,a []woofRunner和a一样好[]woofer),
  • 向它写(对于写woofRunners,a []woofer和a一样好[]woofRunner),
  • 或两者兼有(两者都不可以替代)。

考虑一下,如果Go确实接受协变方式的slice参数,并且有人跟随并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愿意将a []*dog视为a []woofer,我们也会*wolf*dog此处的数组中加上a
。某些语言通过对尝试的数组插入或覆盖进行运行时类型检查来防止此类事故发生,但是由于Go阻止了我们做到这一点,因此不需要这些额外的检查。

2020-07-02