编程沉思录

编程沉思录

马上订阅 编程沉思录 RSS 更新: https://www.cyhone.com/atom.xml

Go 1.22 可能将改变 for 循环变量的语义

2023年11月29日 13:05

几乎世界上每个 Golang 程序员都踩过一遍 for 循环变量的坑,而这个坑的解决方案已经作为实验特性加入到了 Go 1.21 中,并且有望在 Go 1.22 中完全开放。

举个例子,有这么段代码:

1
2
3
4
5
6
7
8
var ids []*int
for i := 0; i < 10; i++ {
ids = append(ids, &i)
}

for _, item := range ids {
println(*item)
}

可以试着在 playgound 里面运行下:go.dev/play/p/O8MVGtueGAf

答案是:打印出来的全是 10。

这个结果实在离谱。原因是因为在目前 Go 的设计中,for 中循环变量的定义是 per loop 而非 per iteration。也就是整个 for 循环期间,变量 i 只会有一个。以上代码等价于:

1
2
3
4
5
var ids []*int
var i int
for i = 0; i < 10; i++ {
ids = append(ids, &i)
}

同样的问题在闭包使用循环变量时也存在,代码如下:

1
2
3
4
5
6
7
var prints []func()
for _, v := range []int{1, 2, 3} {
prints = append(prints, func() { fmt.Println(v) })
}
for _, print := range prints {
print()
}

根据上面的经验,闭包 func 中 fmt.Println(v),捕获到的 v 都是同一个变量。因此打印出来的都是 3。

在目前的 go 版本中,正常来说我们会这么解决:

1
2
3
4
5
var ids []*int
for i := 0; i < 10; i++ {
i := i // 局部变量
ids = append(ids, &i)
}

定义一个新的局部变量, 这样无论闭包还是指针,每次迭代时所引用的内存都不一样了。

这个问题其实在 C++ 中也同样存在: wandbox.org/permlink/Se5WaeDb6quA8FCC

但真的太容易搞错了,几乎每个 Go 程序员都踩过一遍,而且也非常容易忘记。即使这次记住了,下次很容易又会踩一遍。...

剩余内容已隐藏

查看完整文章以阅读更多