🗒️Go Slice/Map 复制陷阱
00 min
2022-3-17
2024-1-21
type
status
date
slug
summary
tags
category
icon
password

信件内容

为什么Go的slice有“复制陷阱”,而map却没有?
假设我们有一个以 slice 作为输入参数的函数,如果在函数中展开 slice,则只会改变复制的 slice 结构,而不是原来的 slice 结构。原来的slice仍然指向原来的数组,而函数中的slice因为扩容而改变了数组指针。
notion image
看一下输出,如果slice是通过“引用”传递的,它就不会是[1 2]。再看这个例子:
notion image
我们在 domap() 中完成的操作成功了!
这难道是陷阱吗?!
我的意思是,map是通过“引用”(*hmap) 传递的,slice是通过“值”(SliceHeader) 传递的。
为什么map和slice被设计成都是内部引用类型,为什么这么不一致呢?
也许这只是一个设计问题,但为什么呢?为什么要这样slice和map?

以下是我对证明为什么map只能通过“引用”传递的猜测:
假设map是按值传递的-> hmap结构类型
(1)init之后:(函数外的hmap)
(2)传递param后,进入函数:(函数内的hmap)
(3)触发扩容后:(函数内的hmap)
但slice不一样!
没有增量迁移,也没有oldbuckets,所以可以使用结构体,因为函数与外部隔离,而map则不然,oldbuckets是在函数外部引用的。
我的意思是,这样设计的最初目的可能是传值,防止直接修改函数中的原始变量,但是map不能传值。一旦将值传递给map,在函数内执行扩容的过程中原始map的数据就会丢失。
如果有人能帮助我解决这个问题,我将不胜感激。多谢!
(我为我垃圾的中式英语感到抱歉,我希望我把问题说清楚了......😢这真的让我很困扰......)

golang-nuts的回信

已经翻译成中文了
@Brian Candler
(顺便说一句:请不要发布屏幕截图。纯文本更易于阅读,并且可以复制粘贴。您也可以使用 https://go.dev/play/ 粘贴代码片段)
我想你已经很好地理解了这个问题。来自 https://golang.org/src/runtime/slice.go 的源代码:
正如您所发现的,这是按值传递的。如果你愿意,当然可以显式传递指向此类值的指针。
这是陷阱吗?!
嗯,这是你必须学习的语言知识,但我认为这是可用设计选项中的最佳选择。
string 和 slice 彼此一致:它们是普通结构,包含指向数据的指针、长度和容量(对于slice)。
是否可以实现值始终是指向结构的“指针”的 slice 和 string ?
我想应该是可以的,但我认为在更深层次上你仍然会遇到类似的问题。
当复制 slice (b := a) 或将其作为函数参数传递时,这将会复制一个指针,因此同一个 slice 将有两个别名,并且对一个slice的修改将对另一个也可见。但是,当进行子切片 (b := a[1:2]) 时,将被迫分配一个新的slice结构。
因此,slice 的行为将完全取决于它的生成方式,因此改变slice可能会也可能不会影响其他slice。我认为整体上会更加混乱。
最重要的是,你仍然会遇到与今天相同的问题:
📌
两个slice可能会也可能不会共享相同的底层缓冲区。
map和channel彼此一致:它们都有比较大的数据结构,并且值是指向该结构的指针。我认为将map值设置为指针是有充分理由的;这意味着你可以做一些方便的事情,比如:
而不是:
但另一方面,map或channel的零值并不是很有用:它是一个 nil 指针,它告诉您有零个元素,但不能添加任何元素。这使得它们在构建包含这些内容的结构时不方便,因为您需要显式地 make(...) 。这是 Go 新手抱怨的另一件事。