Goのforループの変数スコープについてのメモ
以前Pythonのfor-loopでの変数スコープについてちょこっと書いた。
最近Go言語でも似たような(?)問題があることを知ったので備忘録として残しておく。ちなみに私はGo言語は資料を少し読んだことはあるが書いたことはないので正確性は担保できない・・・
こちらのGoのGithub Issueに詳しい:
var all []*Item for _, item := range items { all = append(all, &item) }
などとやるとallに入っているポインタ(appendしているのが&item
でアドレスを取ってる)がすべて同じものになってしまう、という問題。
どういうことかというとfor _, item ...
の部分で作成されるitem
という変数は一回だけメモリ上で確保され、items
の要素が一つずつその同じメモリ箇所を上書きすることで続くブロックの中でアクセスできるようになっている。なので各イテレーションでリストに追加するのが値ではなくアドレスである場合、同一のアドレスが追加されるようになってしまう。
調べてみるとStackOverflowでもすぐに出てきた:
解決方法としては
var all []*Item for i := range items { all = append(all, &items[i]) }
とitems
に添字アクセスすることで、元リストのアドレスを新リストに入れるか、あるいは:
var all []*Item for _, item := range items { item := item all = append(all, &item) }
ブロック内でさらにitem
という変数を導入することでブロックごとに新しくメモリを確保するか。こちらは少し非直感的なコードな気がする。この二つの例は挙動がけっこう違うのでどの挙動を求めているのかはっきり考える必要がある。
さて、元のGitHub Issueではこの挙動を変えるべきか、という議論が行われており、基本的には変える方向に好意的な意見が多い印象。C#でも似たような挙動を修正した経緯があったらしく、その開発者の一人がその修正の経験について語っていたりする。非直感的な挙動が修正されるのはいいことなんだとは思うが、プログラミング言語好きとしてはこういう少し謎な挙動があると気分が盛り上がるので修正されてしまっては少し寂しい気もしてしまう。