🐯
for-rangeでポインタを使うと値が全て同じになる(Go言語)
何が起きたか
for文でGoの構造体をスライスに入れた後にあるフィールドが全て同じ値になっていた。
原因
Goのfor-rangeでは同じ変数(アドレスが同じ変数)に値を代入し続けて、forループを回しているのでポインタを利用すると全て同じアドレスを参照していることになる。
Goに限らずポインタを扱う言語では同様の注意が必要そう。
実例
誤り
type Data struct {
	A string
	B string
	C string
}
type Person struct {
	LastName   string
	FirstName  string
	MiddleName *string
}
people := []Data{
	Data{A: "山田", B: "太郎", C: ""},
	Data{A: "田中", B: "花子", C: "ゴーファー"},
}
var pp []Person
for _, person := range people {
	pp = append(pp, Person{
		LastName:   person.A,
		FirstName:  person.B,
		MiddleName: &person.C,
	})
}
fmt.Println(pp[0].LastName, pp[0].FirstName, *pp[0].MiddleName)
// => 山田 太郎 ゴーファー
fmt.Println(pp[1].LastName, pp[1].FirstName, *pp[1].MiddleName)
// => 田中 花子 ゴーファー
forの後のpersonにpeopleからとってきた値を代入し続けている。そのため&person.Cはループ中、常に同じアドレスになっている。
最終的にperson[2]に代入されたのは「ゴーファー」なので、ループを抜けた後にアドレスを参照して値を確認すると全てが「ゴーファー」になってしまう。
実際にはGoで利用している構造体(Entity)とgRPCの構造体への変換で上記の問題が発生しました。
修正方法
for i := 0; i < len(people); i++ {
	pp = append(pp, Person{
		LastName:   people[i].A,
		FirstName:  people[i].B,
		MiddleName: &people[i].C,
	})
}
上記のように、元のスライスのアドレスで保存していけば意図した通りに動作します。
Discussion