メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#78456likely_accept

maps: add Identical func

ステータス変更: active likely_accept

要約

AIによる要約であり、誤りを含む場合があります。

概要

maps パッケージに Same 関数を追加する提案。2つのマップ変数が同一のハッシュテーブルを指しているかどうか(参照の同一性)を安全かつ効率的に検査できるようにする。

ステータス変更

activelikely_accept
2026年6月25日のProposal Review Meetingにて、関数名を当初案の Identical から Same に変更することで合意し、likely accept に移行した。コアチームは関数の有用性そのものには異論なく、主に命名の問題を解消した上でほぼ承認の見通しとなった。

技術的背景

現状の問題点

Goのマップは内部的にはハッシュテーブルへの参照(ポインタ)として実装されている。同一の make 呼び出しやマップリテラルから生まれた2つのマップ変数は、同じハッシュテーブルを共有しており、一方への変更がもう一方にも反映される。しかしGo言語は == によるマップの比較を禁止しており、2つのマップが同一の参照であるかを直接調べる手段がなかった。
現状のワークアラウンドは以下のように reflectunsafe を使う必要があり、安全な操作にもかかわらず複雑なコードを書かざるを得なかった。

// 現状: reflectを使った参照同一性の確認(非効率かつ冗長)
reflect.ValueOf(x).UnsafePointer() == reflect.ValueOf(y).UnsafePointer()

さらに関連Issue #71388 では、maps.Equal(m, m) のように同一マップを渡した場合にO(1)で処理できないというパフォーマンス問題も指摘されており、参照同一性チェックの需要が裏付けられている。

提案された解決策

maps パッケージに Same 関数を追加する。実装はポインタ比較1命令(CMP命令)にコンパイルされる。2つのマップが異なる型パラメータを持つ場合にも対応できるよう、型パラメータを MXMY と2つに分けた設計になっている。

// After: maps.Sameで参照の同一性を確認
func Same[MX, MY ~map[K]V, K comparable, V any](x MX, y MY) bool {
    type pointer = unsafe.Pointer
    return *(*pointer)(pointer(&x)) == *(*pointer)(pointer(&y))
}

これによって何ができるようになるか

参照の同一性チェックが標準ライブラリから安全かつ明示的に利用できるようになる。

コード例

// Before: 集合のunion関数(ワークアラウンドが必要)
func union(x, y Set) Set {
    // 同一マップか確認する安全な方法がなく、最適化できない
    z := make(Set)
    maps.Copy(z, x)
    maps.Copy(z, y)
    return z
}
// After: maps.Sameによる最適化
func union(x, y Set) Set {
    if maps.Same(x, y) {
        return x // 同一参照なら即座に返せる
    }
    z := make(Set)
    maps.Copy(z, x)
    maps.Copy(z, y)
    return z
}

主なユースケース:

  • 集合演算の最適化: unionintersect などの関数で、両引数が同一マップなら短絡評価が可能になる
  • キャッシュ・メモ化: 同じマップを参照しているかを効率よく確認し、不要なコピーを避ける
  • デバッグ・テスト: 関数がマップのコピーを返しているのか、同じ参照を返しているのかを検証する

議論のハイライト

  • 命名の変遷: 当初の提案名は Identical だったが、「identical twins(一卵性双生児)は同一人物ではない」「go/typestypes.Identical は型理論上の等価性を指す(参照同一性ではない)」といった指摘から、誤解を招くとして Same に変更された。IsAliasedAliasedmaps.Same などの代替案も議論された
  • nil同士の扱い: nil == nil はtrue(両方nilなら同一のnullポインタ)とするのが自然であり、言語仕様の m == nil と一致する挙動として合意された
  • スライスへの拡張: slices.Identical を同時に追加する案も出たが、スライスは長さ・容量・ポインタを組み合わせた複雑な参照構造を持つため、別提案として切り離された
  • NaNに関する注意: 浮動小数点数をキーとするマップで Same(x, y) を使った最適化を行う場合、NaN != NaN の性質により予期しない動作が起きる可能性があることをドキュメントで明示
  • 型パラメータの設計: 呼び出し側が2つの異なる型パラメータでマップを持つケース(MXMY が異なる型だが同じ基底型)に対応するため、型パラメータを2つに分けた最終シグネチャを採用した

関連リンク