Goのマップで発生する「cannot take the address of」エラーの解決方法

intermediate🔷 Go2026-07-24| Go (Golang) 全バージョン

Error Message

cannot take the address of m["key"]
#ポインタ#マップ#メモリ管理#golang

問題の発生

マップに構造体を格納し、ポインタ経由でデータを変更する関数に渡したいというケースはよくあります。マップのエントリに対して直接アドレス演算子(&)を使いたくなるかもしれませんが、直感的ではあってもコンパイラはそれを許可しません。

type User struct {
    Name string
    Age  int
}

m := map[string]User{
    "id123": {Name: "Alice", Age: 30},
}

// この行でコンパイルエラーが発生します
updateUser(&m["id123"])

コンパイラは、次のような具体的なメッセージを表示して停止します:

./main.go:15:13: cannot take the address of m["id123"]

&getSomeValue()のように、関数の戻り値のアドレスを取得しようとする際にも、同様の壁に突き当たります。これらはいずれも「アドレス指定可能性(addressability)」という同じ根本的な理由で失敗します。

なぜGoはこれを制限するのか

Goのマップ値は、マップが動的なエンジンであるため、**アドレス指定可能(addressability)**ではありません。内部的には、Goはマップデータを「バケット」に格納します。マップが大きくなり、負荷係数(load factor)が約6.5に達すると、「再ハッシュ(rehash)」がトリガーされます。このプロセス中、Goは新しいバケットのセットを割り当て、既存の要素を新しいメモリ位置に移動させます。

もしGoがマップ値のアドレス取得を許可していれば、特定のメモリスロットへのポインタを保持することになります。マップが拡張してそのバケットを退避させた瞬間、ポインタは古い無効なデータを指すことになります。これは追跡が非常に困難な非決定的(non-deterministic)なバグの原因となります。メモリ安全性を確保するため、Goの開発チームは設計上、マップの要素をアドレス指定不可にしました。

関数の結果が制限されているのは、別の理由によります。戻り値は多くの場合、CPUレジスタや一時的なスタック位置に存在する一時的な「右辺値(rvalue)」です。そのアドレスを取得するのは危険です。なぜなら、その値の保存場所はコードの実行が終わるとすぐに消滅する可能性があるからです。

解決方法

解決策1:一時変数を使用する

最も手っ取り早い修正方法は、マップの値をローカル変数に移すことです。スタック上の変数は安定しており、アドレス指定可能です。

// 1. 値をローカル変数に取り出す
user := m["id123"]

// 2. 変数のアドレスを渡す
updateUser(&user)

// 3. 変更をマップに書き戻す
m["id123"] = user

この方法は安全ですが、2回のコピーが発生します。もし構造体が非常に大きい場合(例:10KBのバイナリデータなど)、これを毎秒数千回行うとパフォーマンスに影響を与える可能性があります。

解決策2:ポインタのマップを使用する

マップの値を頻繁に変更する必要がある場合は、生の構造体ではなくポインタを格納するようにマップの定義を変更します。これはGoにおける可変コレクションの標準的な手法です。

// map[string]User を map[string]*User に変更
m := map[string]*User{
    "id123": &User{Name: "Alice", Age: 30},
}

// &演算子を使わずに直接ポインタにアクセス
val := m["id123"]
updateUser(val)

マップが再ハッシュされる際、移動するのはポインタです。実際のUser構造体はメモリ上の元の場所に留まります。これによりコピーの必要がなくなり、アドレス指定可能性の問題を完全に回避できます。

解決策3:関数の戻り値を処理する

&foo.GetStatus()のような関数の戻り値についても、解決策は同じです。まず結果を変数に代入して、スタック上に恒久的な場所を与えます。

// 失敗: &someFunction()

// 成功:
val := someFunction()
result := &val

確認方法

go buildを実行してください。エラーが消えていれば、コンパイラは満足しています。ただし、解決策1を選択した場合は、ロジックを再確認してください。よくある間違いは、ローカル変数を更新したものの、マップへの再代入を忘れてしまうことです。最後の代入がなければ、マップの内容は変更されないままです。

temp := m["key"]
temp.Age = 31
// m["key"] = temp がないと、マップには Age: 30 が保持されたままになります
m["key"] = temp

まとめ

- **マップは揮発的:** 要素は拡張時に移動するため、そのメモリ位置は永続的ではありません。
- **安全第一:** Goのアドレス指定可能性ルールは、ダングリングポインタやメモリ破損を防ぐために存在します。
- **適切なマップ型を選択する:** 読み取り中心の小さなデータには`map[K]V`を、フィールドの更新が必要な場合や大きな構造体を扱う場合は`map[K]*V`を使用しましょう。

Related Error Notes