译|There Are No Reference Types in Go

289 阅读3分钟

有一天,我用谷歌搜索一个 Go 问题,谷歌将我引导到  Go FAQ 页面。问题解决后,我阅读了整个 FAQ。

这是一次很棒的阅读,我从文章中学到了很多。但我注意到一个问题, 为什么数组是值,而 map、slice 和 channel 是引用?答复如下:

此话题历史久远。在早期,map 和 channel 都是语法指针,不能声明和使用非指针实例。此外,我们在竭尽全力探索数组如何工作。最终,我们认为指针和值的严格分离使语言更难使用。将这些类型更改为对关联的共享数据结构的引用,就解决了这些问题。改变给语言增加了一些令人遗憾的复杂性,但却对可用性产生了很大的影响:Go 一经推出,就成为了一种更高效、更舒服的语言。

令我惊讶的是,Go 官方文档仍在使用“引用类型”的概念,因为自 2013 年 4 月 3 日以来,“引用类型”的概念已从 Go 规范中完全删除。现在 Go 规范中有 10 个“引用”词,没有一个代表“引用类型”的概念。

另一个惊喜是这句话:

...指针和值的严格分离使该语言更难使用。...

此答复将指针和值视为两个不兼容的概念。但是,Go 规范将指针视为特殊值,指针被称为“指针值”。值只是类型的实例。显然,Go 规范中“指针”一词的定义很好。我认为如果使用“指针值和非指针值”会更好。

所以,我认为此答复给 Go 社区带来了很多困惑。它与当前 Go 规范冲突,并且打破了概念的一致性。

谈回第一个惊喜,我认为称呼   map/slice/channel 值为引用值完全没有必要。不仅因为 “reference” 这个词在编程世界中被滥用了,还因为 map/slice/channel 值只是普通的正常值

以下是 map/slice/channel 类型的内部声明:

Type FamilyType Declaration
mapstruct {
m *internalHashtable
}
channelstruct {
c *internalChannel
}
slicestruct {
array *internalArray
len   int
cap   int
}

请注意,上面的声明可能不完全与官方或非官方的 Go 实现中的声明相同。Go 实现可以直接使用指针表示 map 和 channel 的值,但 Go 规范/编译器永远不会将它们视为指针。因此,你可以放心的将 map/slice/channel 类型视为上面声明的指针包装类型,而不会有任何问题。

从上面的声明,很容易得出结论:map/slice/channel 只是包含一个非导出指针字段的结构类型。将它们称为引用类型是完全没有必要的。

Map 和 slice 类型与一般结构类型确实有一个区别。与一般结构类型不同,对于 map 或 slice 类型 T,T{}  不是 T 的零值。但这不是将 map 或 slice 类型拆分为新的引用类型类别的好理由。

通过理解 Go 的以下两个规则:

  • map/slice/channel 值只是普通的指针包装结构的值
  • 所有赋值,包括参数传递等,都是浅值复制(指针指向的值不会被复制)

Gopher 应该清楚地理解赋值中的 dest 和 source  map/slice/channel 值将共享被包装的指针所指向的同一底层数据。

概念是用来帮助程序员理解语言的机制,而不是混淆他们。值、指针值和非指针值的概念足以让 Gopher 理解 Go。

我希望 Go 文档不会破坏概念定义的一致性。

原文:www.tapirgames.com/blog/golang…