Go Slice/Map 复制陷阱

信的内容是我 22 年问 Google Golang Nuts 的问题以及得到的回复,原信链接:groups.google.com/g/golang-nu...

信件内容

为什么Go的slice有"复制陷阱",而map却没有?

假设我们有一个以 slice 作为输入参数的函数,如果在函数中展开 slice,则只会改变复制的 slice 结构,而不是原来的 slice 结构。原来的slice仍然指向原来的数组,而函数中的slice因为扩容而改变了数组指针。

看一下输出,如果slice是通过"引用"传递的,它就不会是1 2。再看这个例子:

我们在 domap() 中完成的操作成功了!

这难道是陷阱吗?!

我的意思是,map是通过"引用"(*hmap) 传递的,slice是通过"值"(SliceHeader) 传递的。

为什么map和slice被设计成都是内部引用类型 ,为什么这么不一致呢?

也许这只是一个设计问题,但为什么呢?为什么要这样slice和map?


以下是我对证明为什么map只能通过"引用"传递的猜测:

假设map是按值传递的-> hmap结构类型

(1)init之后:(函数外的hmap)

go 复制代码
hmap.buckets = bucketA
hmap.oldbuckets = nil

(2)传递param后,进入函数:(函数内的hmap)

go 复制代码
hmap.buckets = bucketA
hmap.oldbuckets = nil

(3)触发扩容后:(函数内的hmap)

go 复制代码
hmap.buckets = bucketB
hmap.oldbuckets = bucketA

但slice不一样!

没有增量迁移,也没有oldbuckets, 所以可以使用结构体,因为函数与外部隔离,而map则不然,oldbuckets是在函数外部引用的。

我的意思是,这样设计的最初目的可能是传值,防止直接修改函数中的原始变量,但是map不能传值。一旦将值传递给map,在函数内执行扩容的过程中原始map的数据就会丢失。

如果有人能帮助我解决这个问题,我将不胜感激。多谢!

(我为我垃圾的中式英语感到抱歉,我希望我把问题说清楚了......😢这真的让我很困扰......)

golang-nuts的回信

已经翻译成中文了

@Brian Candler

(顺便说一句:请不要发布屏幕截图。纯文本更易于阅读,并且可以复制粘贴。您也可以使用 go.dev/play/ 粘贴代码片段)

我想你已经很好地理解了这个问题。来自 golang.org/src/runtime... 的源代码:

go 复制代码
type slice struct {
      array unsafe.Pointer
      len   int
      cap   int
}

正如您所发现的,这是按值传递的。如果你愿意,当然可以显式传递指向此类值的指针。

这是陷阱吗?!

嗯,这是你必须学习的语言知识,但我认为这是可用设计选项中的最佳选择。

string 和 slice 彼此一致:它们是普通结构,包含指向数据的指针、长度和容量(对于slice)。

是否可以实现值始终是指向结构的"指针"的 slice 和 string ?

我想应该是可以的,但我认为在更深层次上你仍然会遇到类似的问题。

当复制 slice (b := a) 或将其作为函数参数传递时,这将会复制一个指针,因此同一个 slice 将有两个别名,并且对一个slice的修改将对另一个也可见。但是,当进行子切片 (b := a[1:2]) 时,将被迫分配一个新的slice结构。

因此,slice 的行为将完全取决于它的生成方式,因此改变slice可能会也可能不会影响其他slice。我认为整体上会更加混乱。

最重要的是,你仍然会遇到与今天相同的问题:

两个slice可能会也可能不会共享相同的底层缓冲区。

map和channel彼此一致:它们都有比较大的数据结构,并且值是指向该结构的指针。我认为将map值设置为指针是有充分理由的;这意味着你可以做一些方便的事情,比如:

go 复制代码
m["foo"] = "bar"

而不是:

go 复制代码
m = m.set("foo", "bar") // compare: s = append(s, "foo")

但另一方面,map或channel的零值并不是很有用:它是一个 nil 指针,它告诉您有零个元素,但不能添加任何元素。这使得它们在构建包含这些内容的结构时不方便,因为您需要显式地 make(...) 。这是 Go 新手抱怨的另一件事。

相关推荐
程序员爱钓鱼3 小时前
Go 编程实战:函数 Function——参数、返回值与代码复用
后端·面试·go
名字还没想好☜19 小时前
Go 1.23 range-over-func 迭代器实战:自定义可迭代类型、提前退出与惰性求值
开发语言·后端·golang·go·迭代器
赫媒派1 天前
Go 1.27 来了:泛型方法补齐,JSON 提速不踩坑
后端·go·敏捷开发
小满zs1 天前
Go语言第九章(错误处理)
后端·go
程序员爱钓鱼1 天前
Go 编程实战:指针 Pointer——理解地址、取址与解引用
后端·面试·go
Flynt2 天前
Go 1.27 升了一波,泛型方法和 JSON v2 真香但有个坑
后端·go
用户125758524362 天前
对象存储 URL 为什么别到处拼:后台附件预览要验这一层
后端·go·ai编程
名字还没想好☜2 天前
Go 的 database/sql 连接池实战:SetMaxOpenConns 怎么配、连接泄漏怎么查
数据库·sql·golang·go·数据库连接池
王中阳Go2 天前
杰富瑞实测 8 款 AI Agent,国产千问 95 分登顶:我连夜把项目的 OpenAI 硬编码全拆了
人工智能·go
程序员爱钓鱼2 天前
Go 编程实战:Map——使用 Key-Value 管理键值数据
后端·rust·go