golang 1.22からこの問題は発生しなくなっています 参考: https://go.dev/blog/go1.22
golang 1.22未満の goのforループ変数は同じ変数を共有します。
ループ内の変数の処理を後から利用しようとするとループの最後の値になってしまいます。deferとかの時にバグをうみやすいので注意が必要です。
// loop_scope.go
package main
import (
"fmt"
)
func main() {
strings := []string{"a", "b", "c"}
var printFuncList []func()
// ループ変数sは各ループ処理で使い回されるので、無名関数からは同じ変数の値が表示されてしまいます
for _, s := range strings {
printFuncList = append(printFuncList, func() {
fmt.Printf("s = \"%s\" (%p)\n", s, &s)
})
}
// 何らかの処理
for _, printFunc := range printFuncList {
printFunc()
}
}
実行すると、最後の変数の値の"c"が表示されてしまいます。
// 実行結果(1.21.7 で確認)
go run loop_scope.go
s = "c" (0x14000010070)
s = "c" (0x14000010070)
s = "c" (0x14000010070)
// 実行結果(1.22.0 で確認)
go run loop_scope.go
s = "a" (0x14000090040)
s = "b" (0x14000090060)
s = "c" (0x14000090090)
そのため、変数宣言をする必要があります。
// loop_scope.go
package main
import (
"fmt"
)
func main() {
strings := []string{"a", "b", "c"}
var printFuncList []func()
for _, s := range strings {
s := s // この変数宣言が必要
printFuncList = append(printFuncList, func() {
fmt.Printf("s = \"%s\" (%p)\n", s, &s)
})
}
// 何らかの処理
for _, printFunc := range printFuncList {
printFunc()
}
}
今度は、"a", "b", "c"とそれぞれの変数の値の"c"が表示される用になります。
// 実行結果
go run loop_scope.go
s = "a" (0x14000102020)
s = "b" (0x14000102040)
s = "c" (0x14000102070)