为什么我不能在 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 不会编译尝试的调用,报告以下内容:

不能使用狗(类型 []woofRunner) 作为类型 []woofer 来论证 allWoof

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

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

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

(我不确定这是否与 Go 常见问题解答和 Stack Overflow 上的各种问题中解释的情况相同,人们询问将类型T转换为interface{}。切片/数组中的每个指针都指向可直接转换为另一种类型的类型。使用这些指针应该是可能的,原因与将dog[0]传递给"oneWoof"的原因相同。

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

注2:关于可转让性规则:

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

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

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

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

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

问题是该函数是否要求切片参数

  • 从中读取(对于阅读wooferS,[]woofRunner[]woofer一样好),
  • 信给它(写woofRunnerS,[]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 参考

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

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

所以woofRunner并不等同于woofer,这导致我们[]woofRunner不等同于[]woofer

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

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

相关内容

最新更新