Go开发者常犯的语法错误之一,便是for循环作用域的问题,官方预计要通过限缩for循环作用域的范围,来避免这个问题所导致的代码错误。 现在官方已经在Go 1.21中预览变更,并预计要在Go 1.22中正式修正。
当开发者在编写Go程序时,常遇到的问题发生在for循环,误以为循环变量的作用域是每次迭代,但实际上作用域确是整个循环。 官方用以下代码为例,开发者可能期望这段代码会输出 “a”、“b”、“c”的某种顺序排列,但实际上可能输出的是“c”、“c”、“c”。

由于所有Goroutine都共用同一个循环变量,因此当迭代结束时,闭包(Closure)仍会保留对循环变量的参照,但该变量已经变成开发者不想要的新值。
虽然这样的问题经常发生在并行性(Concurrency)运算时的闭包中,但实际上即便没有Goroutine,但仍然还是会发生。 官方提到,这种错误常在企业的生产环境中出现,例如,Let’s Encrypt的公开文件就记录过此类错误。 虽然目前已经有工具可以识别这些错误,但是仍很难分析变量的参照是否超过迭代范围,因此还是可能发生误判或遗漏的状况。
当开发者不熟悉这种作用域特性时,可能会意外地犯错,特别是在Goroutine与闭包结合使用的时候。 这并非是Go语言的错误,而是Go的设计决策,但由于太容易引起误解,因此Go团队决定进行调整。
在Go 1.22版本中,官方计划更改for循环,使变量具有每次迭代的范围,这样的修改会让上述案例不再存在缺陷,也能够终结类似的问题,同时开发者也不再需要使用工具进行分析和更改。
由于需要维持现有代码的向后兼容性,新的语义只适用于go.mod文件宣告为Go 1.22或更高版本的模块套件中,官方提到,他们将以模组作为基础,进而让开发者能够自主控制更新到新语义的节奏,同时,开发者也能够使用-go:build指令来控制每个文件的状况。
旧的代码行为将照旧,修正只会适用更新的代码,Go 1.21不会尝试编译宣告为Go 1.22或更高版本的代码,由于Go 1.20.8和Go 1.19.13两个版本也存在同样for循环问题,因此用新语义编写的代码,也同样不会使用这些旧版本进行编译,除非开发者使用非常旧且不受支持的Go版本。