前提
Go 言語のポインタレシーバに関する質問です。
他言語(C, Python, PHP)の経験はありますが、Go 言語は A Tour of Go を1周したくらいの知識です。
質問
以下のように、ポインタレシーバを受け取りクロージャーを返すメソッド say_closure があります。
これを、関数のスライスに append で格納していく処理を想定しています。
その際に、クロージャーを直接格納する以下の場合は想定通りの動きになります。
Go
1// https://play.golang.org/p/tUH74eXCG92 2package main 3 4import ( 5 "fmt" 6) 7 8type T struct { 9 N string 10} 11 12func (s *T) say_closure() func() { 13 return func() { 14 fmt.Printf("%s\n", s.N) 15 } 16} 17 18func main() { 19 func_slice := []func(){} 20 21 func_slice = append(func_slice, (&T{"foo"}).say_closure()) 22 23 fmt.Println("--- foo append 前 ---") 24 func_slice[0]() // => "foo" 25 26 func_slice = append(func_slice, (&T{"bar"}).say_closure()) 27 28 fmt.Println("--- bar append 後 ---") 29 func_slice[0](); func_slice[1]() // => "foo" "bar" 30} 31
ここでは、func_slice[0] には T{"foo"} が、func_slice[1] には T{"bar"} が格納され、それぞれのクロージャーが適切に呼ばれています。
しかし、以下のように型 T の変数を中継する形で append を行うと、問題が発生します。
Go
1// https://play.golang.org/p/Ct7ZLMkfst0 2package main 3 4import ( 5 "fmt" 6) 7 8type T struct { 9 N string 10} 11 12func (s *T) say_closure() func() { 13 return func() { 14 fmt.Printf("%s\n", s.N) 15 } 16} 17 18func main() { 19 func_slice := []func(){} 20 var tmp T 21 22 tmp = T{"foo"} 23 func_slice = append(func_slice, tmp.say_closure()) 24 25 tmp = T{"bar"} 26 fmt.Println("--- tmp に代入後 ---") 27 func_slice[0]() // => "bar" 問題:この時点で foo が bar に置き換わっている 28 29 func_slice = append(func_slice, tmp.say_closure()) 30}
個人的には、こちらでも func_slice[0] には T{"foo"} が、func_slice[1] には T{"bar"} が格納されていてほしいのですが、そうはなりません。func_slice[0] も func_slice[1] にも T{"bar"} が格納されています。
スライスの第0要素には T{"foo"}.say_closure() の返り値の関数が入っていたはずなのですが、tmp に T{"bar"} を代入した時点でスライスの第0要素の参照先が変わっていることに困惑しています。
なぜなら、append に指定しているのは tmp そのものではなく tmp.say_closure() なので、say_closure の返り値が格納されているのが自然だと思っているからです。
どこの参照先がスライスに代入され、どこでその参照先が変わるのかが全くイメージできていないため、この問題をどう解釈すればいいのかすら分からない状態です。
いくつかの Go の仕様が重なっている問題かもしれませんが、解説やご指摘頂けると幸いです。
(ドキュメントや書籍のここを読めというコメントでも構いません。)
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/05/24 06:35