在「Fixing for loops in Go 1.22 (go.dev)」看到的,原文在「Fixing For Loops in Go 1.22」這邊。
算是各種有 closure 的程式語言會遇到的經典問題,下面的 Golang 程式在撰寫時會預期輸出 a
、b
、c
(不保證順序),但實際上會輸出 c
、c
、c
,因為變數的 scope 設計原因:
func main() { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
這在 JavaScript 也是個經典的問題,搜「javascript closure loop」就可以看到「JavaScript closure inside loops – simple practical example」這個例子,對應的答案則是有提到 ES6 的 let
因為改變了變數的 scope,可以用來解決這個問題。
剛好 let
也是 best practice 了,所以現在 JavaScript 這邊遇到這個問題的情況會少一些。
回到 Golang 這邊,Golang 是打算改變在 for loop 時,對應變數的 scope 來解決:
For Go 1.22, we plan to change for loops to make these variables have per-iteration scope instead of per-loop scope. This change will fix the examples above, so that they are no longer buggy Go programs; it will end the production problems caused by such mistakes; and it will remove the need for imprecise tools that prompt users to make unnecessary changes to their code.
另外因為這是 breaking compatibility 的改變 (畢竟有些程式碼是清楚知道這個特性而刻意這樣寫的),所以會有對應的措施,只有在有指定 Golang 1.22 或是更新的版本才會用新的 scope 編譯:
To ensure backwards compatibility with existing code, the new semantics will only apply in packages contained in modules that declare go 1.22 or later in their go.mod files. This per-module decision provides developer control of a gradual update to the new semantics throughout a codebase. It is also possible to use //go:build lines to control the decision on a per-file basis.