Golang 1.22版本中for-loop变量语义的变更
Golang 1.22版本中for-loop变量语义的变更 在 Go 1.22 版本中列出的第一个更改是为了修复一个曾多次困扰我(可能还有无数其他人)的意外行为,该更改修改了 for 循环的语义:
此前,由“for”循环声明的变量只创建一次,并在每次迭代中更新。在 Go 1.22 中,循环的每次迭代都会创建新的变量,以避免意外的共享错误。
下面是一个简单的演示,它在 Go 1.21 和 Go 1.22 中会打印出不同的结果:
package main
import (
"fmt"
)
func main() {
var funcs []func()
for i := 0; i < 2; i++ {
funcs = append(funcs, func() {fmt.Printf("%d\n", i)})
}
for _, f := range funcs {
f()
}
}
在 Go 1.22 中,程序的行为符合大多数程序员的预期,打印出 0, 1。闭包使用的是闭包创建时 i 的值。这个变量在循环的每次迭代中都是不同的。
在 Go 1.21 及更早版本中,它打印出 2, 2。 变量 i 只被创建了一次,而使用它的闭包是通过引用来使用它的。由于闭包是在循环执行完毕后执行的,此时 i 的值为 “2”。
我在论坛上搜索了关于旧行为的讨论,找到了这个:
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func TestClosure() {
data := []*field{{"one"}, {"two"}, {"three"}}
for _, v := range data {
go v.print()
}
time.Sleep(3 * time.Second)
}
func TestClosure1() {
data := []field{{"one"}, {"two"}, {"three"}}
for _, v := range data {
go v.print()
}
time.Sleep(…
遗憾的是,我无法在那里添加评论来指出行为的改变。
更多关于Golang 1.22版本中for-loop变量语义的变更的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的——这是一项“破坏性”变更,其副作用可能仅仅是修复一些细微的错误。
更多关于Golang 1.22版本中for-loop变量语义的变更的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Go 1.22 中 for 循环变量语义的变更是 Go 语言的一个重要改进,解决了长期存在的闭包捕获循环变量时的意外行为问题。以下是详细说明和示例:
在 Go 1.21 及更早版本中,for 循环声明的变量(如 i 或 v)在循环的整个生命周期中只创建一次,每次迭代更新其值。这会导致闭包捕获的是变量的引用,而不是迭代时的值。例如:
package main
import "fmt"
func main() {
var funcs []func()
for i := 0; i < 2; i++ {
funcs = append(funcs, func() { fmt.Println(i) })
}
for _, f := range funcs {
f()
}
}
在 Go 1.21 中,输出为:
2
2
在 Go 1.22 中,每次迭代都会创建新的变量实例,闭包捕获的是迭代时的值。因此,相同的代码在 Go 1.22 中输出:
0
1
另一个常见场景是在 goroutine 中使用循环变量。在 Go 1.21 中:
package main
import (
"fmt"
"time"
)
func main() {
data := []string{"one", "two", "three"}
for _, v := range data {
go func() {
fmt.Println(v)
}()
}
time.Sleep(time.Second)
}
输出可能为:
three
three
three
在 Go 1.22 中,每次迭代的 v 是新变量,输出为:
one
two
three
对于结构体切片,类似的问题也会被修复。在 Go 1.21 中:
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []field{{"one"}, {"two"}, {"three"}}
for _, v := range data {
go v.print()
}
time.Sleep(time.Second)
}
在 Go 1.21 中,可能输出重复值。在 Go 1.22 中,每次迭代的 v 是新变量,输出正确。
此变更仅影响在循环体中声明的变量,不影响循环后使用的变量。例如:
package main
import "fmt"
func main() {
var i int
for i = 0; i < 2; i++ {
// i 是外部变量,行为不变
}
fmt.Println(i) // 输出 2
}
对于需要向后兼容的代码,Go 1.22 提供了 GOEXPERIMENT=loopvar 环境变量来控制行为,但建议直接适配新语义。

