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

Go Proposal Weekly Digest

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

#78456accepted

maps: add Identical func

ステータス変更: likely_accept accepted

要約

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

概要

Goのmapsパッケージに、2つのマップが同一のハッシュテーブル(同じ実体)を参照しているかどうかを判定する関数を追加するproposalです。これによりreflectやunsafeを使わずに安全かつ高速にマップの参照同一性を検査できるようになります。

ステータス変更

likely_acceptaccepted
2026-06-25のレビュー会議で「likely accept」と判断された後、追加のコメント(コメント文言に関する軽微な提案のみ)はあったものの合意内容に変更はなく、2026-07-08の会議で @aclements により「no change in consensus」として正式にacceptedとなりました。なお、Issueタイトルは当初の関数名Identicalのままですが、議論の結果、実際に採用された関数名はSameです。

技術的背景

現状の問題点

Goのマップは参照型(ハッシュテーブルへの参照)であり、同じmake(M)呼び出しやマップリテラルから作られた2つの変数は同一の実体を指すことがあります。しかしGo言語仕様では、マップの中身の混同を避けるため==によるマップ同士の比較が禁止されています(nilとの比較のみ可能)。そのため、2つのマップ変数が同じ実体を参照しているかを調べる標準的で安全な方法がありませんでした。

// 現在のワークアラウンド(unsafe/reflectを使う必要がある)
same := reflect.ValueOf(x).UnsafePointer() == reflect.ValueOf(y).UnsafePointer()

このコードは非効率なコンパイル結果になる上、本来totally safeな操作であるにもかかわらずreflectunsafeを扱う必要があり、利用のハードルが高い点が問題視されていました。

提案された解決策

mapsパッケージに以下のジェネリック関数を追加します(内部でunsafeポインタ比較を1回のCMP命令に変換)。

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))
}

型パラメータは、呼び出し側でxy自体が型パラメータになっているケース(マップの具体型が異なる場合)にも対応できるよう2つ用意されています。nil同士はnil == nilの言語仕様と整合するよう「同一」と判定されます。

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

  • 2つのマップが同じ実体を指すかどうかを、安全かつ効率的なAPIで判定できます。
  • 例えばunion(x, y)のような集合演算関数で、xyが同一実体であれば計算をスキップするショートカットの実装が容易になります。
  • キャッシュや共有状態の検出、デバッグ時の意図しないエイリアシングの発見などに活用できます。

コード例

// Before: reflect/unsafeを使ったワークアラウンド
same := reflect.ValueOf(x).UnsafePointer() == reflect.ValueOf(y).UnsafePointer()
// After: maps.Same を使った書き方
import "maps"
type Set = map[float64]struct{}
// union は2つの集合の和集合を返す
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
}

議論のハイライト

  • 命名論争: 提案時の名前Identicalは「Equalと混同しやすい」「types.Identical(型理論上の同一性)と紛らわしい」「identical twinsは同一のアイデンティティを持たないため語感が誤解を招く」といった懸念が @mateusz834 らから出され、複数の代替案(IsAliased/Aliased/Same/IsSame)が議論されました。最終的にレビュー委員会(@adonovan ら)がSameの方が優れているとして採用しました。
  • NaNに関する注意: Sameはポインタ比較に過ぎず値の等価性は保証しないため、浮動小数点NaNをキーに持つマップに対する最適化(ショートカット)が意図しない挙動を招く可能性があり、doc commentに明記することになりました。
  • slicesパッケージへの拡張は対象外: スライスの「参照同一性」はlen/cap/ポインタなど値意味論と参照意味論が絡み合い複雑(overlapsのような既存の内部関数はユースケースが限定的)であるため、@adonovan の判断により本proposalのスコープ外とされ、必要なら別proposalとすることになりました。
  • 型パラメータ設計の変遷: 当初はMX ~map[K]VX, MY ~map[K]VYという設計でしたが、呼び出し側が既にジェネリックな場合に対応するためMX, MY ~map[K]Vという共通のK/V型パラメータを持つ形に変更されました。
  • nilの扱い: nilマップ同士は「同一」と判定される(m == nilの言語仕様との整合性を優先)ことが確認され、x == nil || y == nilで早期にfalseを返すような追加チェックは不要と結論づけられました。

関連リンク