Computer368520 views
中学英語812106 views
高校日本史190647 views
小学社会310694 views
いろは3014035 views
ヒストリア291495 views
高校物理160596 views
雑学1473736 views
高校生物552029 views
中学社会669043 views
Help
Tools

English

Go のマップの注意点と落とし穴

Go のマップにはいくつかの落とし穴がある。これらを知っておくことで、バグを未然に防げる。

nil マップへの書き込み

var で宣言しただけのマップは nil であり、書き込むと panic になる。

var m map[string]int
m["key"] = 1 // panic: assignment to entry in nil map

必ず make かリテラルで初期化してから使う。

m := make(map[string]int)
m["key"] = 1 // OK

構造体フィールドの直接更新はできない

マップの値が構造体の場合、フィールドを直接更新できない。

type Person struct {
    Name string
    Age  int
}

people := map[string]Person{
    "alice": {Name: "Alice", Age: 30},
}

people["alice"].Age = 31 // コンパイルエラー

これはマップの値がコピーとして返されるためだ。解決策は2つある。

一度取り出して代入し直す

値を変数に取り出し、変更してから再代入する。

ポインタを値にする

マップの値をポインタにすれば、直接フィールドを更新できる。

// 方法1: 取り出して再代入
p := people["alice"]
p.Age = 31
people["alice"] = p

// 方法2: ポインタを使う
people := map[string]*Person{
    "alice": {Name: "Alice", Age: 30},
}
people["alice"].Age = 31 // OK

並行アクセスは安全ではない

複数のゴルーチンから同時にマップを読み書きすると、データ競合が発生する。

m := make(map[int]int)

// 危険なコード
go func() {
    for i := 0; i < 1000; i++ {
        m[i] = i
    }
}()

go func() {
    for i := 0; i < 1000; i++ {
        _ = m[i]
    }
}()
// fatal error: concurrent map read and map write

並行アクセスが必要な場合は sync.Mutex か sync.RWMutex で保護する。

var mu sync.RWMutex
m := make(map[int]int)

// 書き込み
mu.Lock()
m[1] = 100
mu.Unlock()

// 読み込み
mu.RLock()
v := m[1]
mu.RUnlock()

マップの比較はできない

マップ同士を == で比較することはできない。

m1 := map[string]int{"a": 1}
m2 := map[string]int{"a": 1}

fmt.Println(m1 == m2) // コンパイルエラー

nil との比較だけは可能だ。

var m map[string]int
fmt.Println(m == nil) // true

マップの内容を比較したい場合は、reflect.DeepEqual を使うか、自分でループして比較する。

import "reflect"

m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"a": 1, "b": 2}

fmt.Println(reflect.DeepEqual(m1, m2)) // true

キーにできない型

スライス、マップ、関数はキーにできない。

// コンパイルエラー
m := map[[]int]string{}      // スライスはNG
m := map[map[int]int]string{} // マップはNG
m := map[func()]string{}      // 関数はNG

配列はキーにできる。

m := map[[3]int]string{
    {1, 2, 3}: "one-two-three",
}

ゼロ値の罠

存在しないキーにアクセスするとゼロ値が返るため、意図しない動作になることがある。

counts := make(map[string]int)

// 存在しないキーでもエラーにならない
counts["apple"]++ // 0 + 1 = 1
counts["apple"]++ // 1 + 1 = 2

fmt.Println(counts["apple"]) // 2

この挙動はカウンターとして使う場合には便利だが、存在チェックが必要な場面ではコンマOK イディオムを使うこと。