KDOC 710: Go 1.21までのforのループ変数はループ全体で1つだった
この文書のステータス
- 作成
- 2026-10-18 貴島
- レビュー
- 2026-10-18 貴島
概要
言語の版がGo 1.21までは、 for のループ変数はループ全体で1つだった。ループの中で作ったクロージャは同じ変数を参照するので、あとで実行するとループを抜けたあとの値が見える。Go 1.22からは、反復ごとに別の変数が作られる。
コンパイラに -lang=go1.21 を渡すと、Go 1.21の言語仕様でコンパイルされる。
go doc cmd/compile | grep -A2 -E '^\s+-lang'
-lang version
Set language version to compile, as in -lang=go1.12.
Default is current version.
package main
import "fmt"
func main() {
var prints []func()
for i := 0; i < 3; i++ {
prints = append(prints, func() { fmt.Print(i, " ") })
}
for _, p := range prints {
p()
}
}
3 3 3
Go 1.22以降の言語仕様だと、反復ごとの値が見える。
package main
import "fmt"
func main() {
var prints []func()
for i := 0; i < 3; i++ {
prints = append(prints, func() { fmt.Print(i, " ") })
}
for _, p := range prints {
p()
}
}
0 1 2
Go 1.26 での言語仕様は、反復ごとに別の変数を宣言すると書いている。
go version
sed -n '/^Each iteration has its own separate declared variable/,/^<\/p>/p' "$(go env GOROOT)/doc/go_spec.html"
go version go1.26.7 linux/amd64
Each iteration has its own separate declared variable (or variables)
[<a href="#Go_1.22">Go 1.22</a>].
The variable used by the first iteration is declared by the init statement.
The variable used by each subsequent iteration is declared implicitly before
executing the post statement and initialized to the value of the previous
iteration's variable at that moment.
</p>
関連
抽象。
- 繰り返しの中で作った参照が、共有された1つの変数を見ていた
- 同じコードでも、どの版の意味で解釈するかで挙動が変わる
関連。
- 補足: KDOC 362: コンパイル可能な最低バージョンと、ビルドに使うバージョンは異なる。go.mod の go 行が、モジュールが前提にする Go の版のセマンティクスを示すことを、仕様書から読んでいる
- 共通: KDOC 437: map deleteはなかった時代がある。どちらもGoの過去の仕様の話
- 引用: KDOC 474: 『Go言語による並行処理』。ゴルーチンの中のクロージャがループ変数を共有する例が載っている
- 疑問: KDOC 653: :=はvarと違い、再宣言でき関数内でのみ使える。for 内外で見た目が
:=で同じでも、挙動が違ったということか - 追加調査: 過去の挙動はどういうメリットがあったのか
- 追加調査: プログラミング言語のわかりにくい挙動が改善された例
- 追加調査: 内部的にどういう変更がなされたか
- 類推: KDOC 131: OpenAPI v3.1でnullableの記法が変わった。同じ書き方でも、どの版の意味で解釈するかで挙動が変わる例