前言
前段时间看到了一个提案,是关于 go for 循环的一个提案,根据提案看到了去年 rsc 在社区发出的讨论,讨论的内容主要是为了解决 for 循环变量的问题,是什么样的问题呢,常见的例子如下:
var all []*Item
for _, item := range items {
all = append(all, &item)
}
这段代码有一个问题,循环结束后,all 的内容是包含了 len(all) 个相同的指针,指针指向迭代的最后一个 item。为什么会发生这种情况呢,因为 item 变量是每个循环的而不是每次迭代的,&item每次迭代都是相同的,并且每次迭代都会被覆盖。
怎么解决呢,最简单的方法就是添加 item := item
这段代码:
var all []*Item
for _, item := range items {
item := item
all = append(all, &item)
}
我在使用 Goroutine 协程时也经常遇到这种问题。这种错误已导致许多公司出现生产问题,包括 Lets Encrypt 公开记录的问题。
go 社区为了解决这个问题,打算将循环变量改为每次迭代,即隐式的添加上面的代码。由于其它一些原因,直到今年的6月才正式决定在 go 1.21 中添加 GOEXPERIMENT=loopvar 进行相应的尝试,并且将在 go1.22 版本中正式推出。 为了确保与现有代码的向后兼容性,新语义将仅适用于声明go 1.22或稍后在其go.mod文件中声明的模块中包含的包。
特性测试
我们通过不同版本运行的结果来对比
package main
import "fmt"
func main() {
var prints []func()
for _, v := range []int{1, 2, 3} {
prints = append(prints, func() { fmt.Println(v) })
}
for _, print := range prints {
print()
}
}
没使用旧版本运行的结果是:
3
3
3
使用 gotip 运行的结果如下:
1
2
3
参考
- https://github.com/golang/go/issues/20733
- https://github.com/golang/go/discussions/56010
- https://github.com/golang/go/issues/60078
- https://tonybai.com/2023/08/20/some-changes-in-go-1-21/
- https://github.com/golang/go/wiki/LoopvarExperiment
- https://go.googlesource.com/proposal/+/master/design/60078-loopvar.md
- https://bugzilla.mozilla.org/show_bug.cgi?id=1619047