emahiro/b.log

勉強記録と書評とたまに長めの呟きを書きます

structに埋め込んだmapの要素を上書きする

やりたいこと

struct内に埋め込んだmapのfieldの値を上書きするという処理を考えます。
下記のようなサンプルコードがあったときに originalSamples のItemsの中身を上書きするような処理です。

type Samples struct {
  Items map[string]string
}

originalSamples := Samples{
  Items: map[string]string{
    "first": "apple",
    "second": "orange",
  },
}

やることはmapの中身を更新するだけなのですが、いくつかハマったのでメモとして残して起きます。

ハマったところ

assignment to entry in nil map が発生した

mapを初期化しなかったケースです。
mapを初期化せずに新規の値をappendしようとすると panic: assignment to entry in nil map のエラーが発生します。

var m Samples

for k, item := range originalSamples.Items{
  item = "iPhone"
  m.Items[k] = item
}

この場合は var で定義するのではなく Sample{} で初期化することが必要になります。

otiai10.hatenablog.com

structを初期化してもassinment to entry in nil map が起きた

structを初期化しても同様のエラーが発生しました。
具体的なコードは以下です。

originalSamples := Samples{
  Items: map[string]string{
    "first":  "pc",
    "second": "book",
  },
}

m := Samples{}

for k, item := range originalSamples.Items {
  item = "iPhone"
  m.Items[k] = item
}

これはなぜ起きるかというと、struct内に埋め込まれているmapはstructをインスタンス化してもnilでになってしまうために assignment to entry in nil map のエラーが発生します。

こちらを回避策を検討した結果、mapをnilしないようにするので下記の2パターンが考えられると思います。

  1. mapをinstace化して後で上書きしたいstructに代入する
  2. structのinstance化と合わせて埋め込まれているmapもinstance化する
// mapをinstance化する
newSamples := Samples{}
m := make(map[string]string)

for k, item := range originalSamples.Items {
  item = "iPhone"
  m[k] = item
}

// structに代入する
newSamples.Items = m
fmt.Printf("%v", newSamples)
// structのinstance化のタイミングでmapも初期化する
newSamples := Samples{
  Items: map[string]string{},
}

for k, item := range originalSamples.Items {
  item = "iPhone"
  newSamples.Items[k] = item
}
fmt.Printf("%v", newSamples)

まとめ

mapを動的に扱いたい場合は、ちゃんと初期化されているか確認する。
structを初期化しても中のpropertyまで初期化されているわけではない。