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 新手抱怨的另一件事。

相关推荐
研究司马懿6 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大20 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo