まずはこれを見てくれ。コイツをどう思う?
func main() {
xs := []int{1, 2, 3}
var cmds []func()
for _, x := range xs {
cmds = append(cmds, func() { fmt.Println(x) })
fmt.Println("append: " + strconv.Itoa(x))
for _, cmd := range cmds {
fmt.Print("func eval: ")
cmd()
}
fmt.Println()
}
}
リストxsの値を利用する無名関数xsを作る。まあ、それなりにオーソドックスな処理だと思います。わたしの期待値としては、こうなるはずでした。
$ go run example_lambda.go
append: 1
func eval: 1
append: 2
func eval: 1
func eval: 2
append: 3
func eval: 1
func eval: 2
func eval: 3
しかし、現実はこうでした。
$ go run example_lambda.go
append: 1
func eval: 1
append: 2
func eval: 2
func eval: 2
append: 3
func eval: 3
func eval: 3
func eval: 3
あれ、各関数の実行結果が全部同じに!? そのループ内で触ってないのに値変わってるんだけど... ダイナミックスコープみたいな動きしてるけど、goってクロージャーだからレキシカルスコープだよね???
と混乱していたところ、@tutumingさんより
@koduki x がforの初期化で1度確保されたあとは代入になるので、全部の関数の中で参照してるxが同じ変数になってるのが原因ですね。 https://t.co/hCIUK7Ve6q
— tutuming (@tutuming) 2016, 2月 11
と、指摘をいただきました。
むむ? ということはXはループ毎に別の変数じゃなくて、ポインタアドレスは全部一緒ってこと?
とりあえず、アドレスを表示してみると...
go run example_lambda.go
append: 1
x addr = %!s(*int=0xc20800a200)
func eval: 1
append: 2
x addr = %!s(*int=0xc20800a200)
func eval: 2
func eval: 2
append: 3
x addr = %!s(*int=0xc20800a200)
func eval: 3
func eval: 3
func eval: 3
おお、確かに一緒だ。でも、ここまでわかれば大丈夫。Goは確か代入時は値で渡すはずだから、こんな感じに変えました。
func main() {
xs := []int{1, 2, 3}
var cmds []func()
for _, x := range xs {
tmp := x
cmds = append(cmds, func() { fmt.Println(tmp) })
fmt.Println("append: " + strconv.Itoa(tmp))
fmt.Printf("x addr = %s, tmp addr = %s\n", &x, &tmp)
for _, cmd := range cmds {
fmt.Print("func eval: ")
cmd()
}
fmt.Println()
}
}
forループの中で宣言したtmpに一度代入して、それを使うようにします。実行結果は以下の通り。
$ go run example_lambda.go
append: 1
x addr = %!s(*int=0xc20800a200), tmp addr = %!s(*int=0xc20800a208)
func eval: 1
append: 2
x addr = %!s(*int=0xc20800a200), tmp addr = %!s(*int=0xc20800a2b8)
func eval: 1
func eval: 2
append: 3
x addr = %!s(*int=0xc20800a200), tmp addr = %!s(*int=0xc20800a3a0)
func eval: 1
func eval: 2
func eval: 3
期待通り、関数内で参照している値が変わりました。tmp変数のアドレスがx変数とは異なり毎回違うのも確認できます。
しかし、分かってしまえば何ともないかもですが、foreach文でxのアドレスが一緒なのは嵌りそうだな... もっとGo力を鍛えないとですね。
それではHappy Hacking!