Golang 1.22 语言规范改动

   3 min read

默认修复循环变量意外共享问题

在 Go 1.21 需要手动通过 GOEXPERIMENT=loopvar 启用的功能,修复循环变量意外共享,现在是默认的。

例如:

func TestLoopVar(t *testing.T) {
	var funcSlice []func()
	for i := 0; i < 3; i++ {
		funcSlice = append(funcSlice, func() {
			print(i)
			print(" ")
		})
	}
	for j := 0; j < 3; j++ {
		funcSlice[j]()
	}
}

在 Go 1.22 之前,以上代码打印的值为 3 3 3 ,现在是 0 1 2 。这是因为 i 只会创建一次,然后在每次迭代中不断赋予新值。

现在每次迭代都会创建一个新的 i,每个 i 的地址都不一样,所以不会意外共享。详见:Go 语言闭包详解 - 第三个例子

range 支持整数

现在可以在 range 后面写一个整数,然后 for 会循环对应的次数,i 从 0 开始递增。例如:

func TestRangeNumber(t *testing.T) {
	for i := range 5 {
		print(i)
		print(" ")
	}
}

以上代码打印的值为 0 1 2 3 4

可以起到简化以下代码的作用:

	for i := 0; i < 5; i++ {
		print(i)
		print(" ")
	}

range 支持函数迭代器

假设我们要实现反向打印切片值的功能,一个简单的实现可能是这样的:

func TestBackward(t *testing.T) {
	s := []string{"hello", "world"}
	for i := len(s) - 1; i >= 0; i-- {
		print(i, s[i])
		print(" ")
	}
}

以上代码会打印1world 0hello

在 Go 1.22,可以通过 GOEXPERIMENT=rangefunc 启用 range 支持函数迭代器。

这样就可以用以下代码实现相同功能:

func Backward[E any](s []E) func(func(int, E) bool) {
	return func(yield func(int, E) bool) {
		for i := len(s) - 1; i >= 0; i-- {
			if !yield(i, s[i]) {
				return
			}
		}
	}
}

func TestLoopFunc(t *testing.T) {
	s := []string{"hello", "world"}
	for i, x := range Backward(s) {
		print(i, x)
		print(" ")
	}
}

可以起到简化以下代码的作用:

	s := []string{"hello", "world"}
	Backward(s)(func(i int, x string) bool {
		print(i, x)
		print(" ")
		return true
	})

参考链接

Go 1.22 Release Notes

Go Wiki: Rangefunc Experiment