Go言語 構造体のmapはポインタを使おう

Go言語楽しいですね。
でも、やっぱりポインタ周りでつまずく事になりました。

マップに入れた構造体が変更できなくて
困っておりました。

valueの変更ができない

personMap := map[int]Person{}
personMap[1] = Person{Id: 1, Name: "aaa", Age: 20}
personMap[2] = Person{Id: 2, Name: "bbb", Age: 21}
personMap[3] = Person{Id: 3, Name: "ccc", Age: 22}

person := personMap[1]
person.Age = 30 // ここで変更を反映させたい

pp.Printf(personMap)

上記のコード、マップに収められている構造体に対して、変更をかけたいんですが
実際には変更が反映されません。

map[int]main.Person{ 
  1: main.Person{
    Id:   1,
    Name: "aaa",
    Age:  20,
  },
  2: main.Person{
    Id:   2,
    Name: "bbb",
    Age:  21,
  },
  3: main.Person{
    Id:   3,
    Name: "ccc",
    Age:  22,
  },
}

そりゃそうなんだろうね〜。
変更したい場合は、ポインタを取得して
アドレスに対して変更をかけないといけないんだけど…

valueのアドレスアクセスは禁止されている

person := &personMap[1] // ここでエラー
person.Age = 30

とできるのかと思いきや、これは出来ないみたいです。
ではでは、直接変更できないもんかねと思うんですが、これもダメみたい。

personMap[1].Age = 30 // これもエラー

cannot assign to struct field personMap[1].Age in map

というエラーがでます。

ダイレクトアクセスがダメな理由

マップ構造は、要素の追加により
アドレス空間を再構築するため、
収められている構造体のアドレスも
途中で変更されるみたいです。

ポインタの場合は、
収められている値は再構築されても、
ポインタ自体の値は変更されないので、
問題ないってことです。

構造体ポインタのmapにしよう

じゃあどうするのか?

構造体のmapを作るときは
構造体のポインタを要素として持たせると良いみたいです。

personMap := map[int]*Person{}                       // ポインタのmapに
personMap[1] = &Person{Id: 1, Name: "aaa", Age: 20}  // 構造体のポインタを格納する
personMap[2] = &Person{Id: 2, Name: "bbb", Age: 21}
personMap[3] = &Person{Id: 3, Name: "ccc", Age: 22}

person := personMap[1]
person.Age = 30

pp.Println(personMap)
map[int]*main.Person{ 
  1: &main.Person{
    Id:   1,
    Name: "aaa",
    Age:  30,
  },
  2: &main.Person{
    Id:   2,
    Name: "bbb",
    Age:  21,
  },
  3: &main.Person{
    Id:   3,
    Name: "ccc",
    Age:  22,
  },
}

ちゃんと、aaaさんのAgeが更新されている事がわかります。
ポインタのmapにしておかないと、取り出すたびに構造体のコピーが生成されているという事なんでしょうね。

参考URL
https://stackoverflow.com/questions/40578646/golang-i-have-a-map-of-structs-why-cant-i-directly-modify-a-field-in-a-struct

おしまい

personMap := map[int]Person{}
personMap := map[int]*Person{}

json::Unmarshal に渡すときは、&personMap という形で渡しますが、
要素が 構造体であろうが、ポインタであろうが正常に動作するようです。

json.Unmarshal(bytes, &personMap)

コメントを残す