maps: add Identical func
要約
概要
mapsパッケージに、2つのマップが同一のデータ構造(ハッシュテーブル)を参照しているかどうかを検査するIdentical関数を追加するproposal。Go言語ではマップは参照型であるにもかかわらず、2つのマップ変数が同じ内部ポインタを指しているかを安全かつ効率的に確認する手段が標準で存在しないという問題を解決する。
ステータス変更
(なし) → active
2026年6月18日の週次Proposal Review Meetingにて、@aclementsによりactiveカラムへの追加が通知された。実装CLがすでに提出されており(go.dev/cl/760800)、APIの最終形も固まってきたことでレビューフェーズへ移行したと考えられる。
技術的背景
現状の問題点
Goのマップは内部的にはハッシュテーブルへのポインタ(参照)として実装されている。同じmake(M)呼び出しやマップリテラルから生成された2つの変数は同一のデータ構造を指すため、一方への変更は他方にも反映される。
しかし言語仕様上、マップは==での比較が禁止されている(内容比較と参照比較を混同させないため)。そのため参照の同一性を確認するには、現状では非効率なreflectを使う必要がある。
// 現状のワークアラウンド(非効率で unsafe が必要)
reflect.ValueOf(x).UnsafePointer() == reflect.ValueOf(y).UnsafePointer()
この操作は本質的に安全なポインタ比較であるにもかかわらず、reflectやunsafeに頼らざるを得ない。また、コンパイル後のコードも非効率になる。
提案された解決策
maps.Identical関数を追加する。これは単純なポインタ比較であり、コンパイラによって1つのCMP命令に最適化される。
最新のAPI提案(#issuecomment-4203140372)では、2つのマップが同じキー型・値型でなくても(型パラメータが異なる場合に対応するため)呼び出せるよう、型パラメータを2つ持つシグネチャが採用されている。
package maps
// Identical reports whether two maps refer to the same data structure.
//
// Beware that some shortcuts based on Identical(x, y) may have surprising
// behavior for maps containing floating-point NaNs, since NaN != NaN.
func Identical[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: reflectを使った非効率なワークアラウンド
import "reflect"
func isSameMap(x, y map[string]int) bool {
return reflect.ValueOf(x).UnsafePointer() == reflect.ValueOf(y).UnsafePointer()
}
// After: maps.Identical を使ったシンプルな記述
import "maps"
// ユースケース1: unionのショートカット
type Set = map[float64]struct{}
func union(x, y Set) Set {
if maps.Identical(x, y) {
return x // 同一参照なら結果は自分自身
}
z := make(Set)
maps.Copy(z, x)
maps.Copy(z, y)
return z
}
// ユースケース2: 意図しないエイリアシングの検出
func safeUpdate(dst, src map[string]int) {
if maps.Identical(dst, src) {
panic("dst and src must not be the same map")
}
maps.Copy(dst, src)
}
// ユースケース3: nilマップの一致確認(nil同士はtrueを返す)
var a, b map[string]int
fmt.Println(maps.Identical(a, b)) // true(どちらもnilポインタ)
議論のハイライト
- 関数名「Identical」への懸念:
Equal(内容比較)と混同されやすいとの指摘が複数あり、Aliased・Same・IsSameなどの代替名も提案された。しかし現時点ではIdenticalのまま進んでいる - 型パラメータの設計変遷: 当初は
MX, MY ~map[K]V(同一K/V)だったが、型パラメータを使う呼び出し側が異なる型を渡せるよう、後にMX ~map[K]VX, MY ~map[K]VYに拡張された - NaNに関する注意: 浮動小数点数をキーとするマップで
Identicalを使ったショートカットを適用すると、NaN != NaNの性質から予期しない動作が起きる可能性があることがドキュメントに明記されている - nilマップの扱い:
nil == nilはtrueであり、2つのnilマップに対してIdenticalがtrueを返すのは正当な動作(nilもポインタの一種)という結論に落ち着いた - slicesパッケージへの同様の追加提案は却下: スライスはポインタ・長さ・容量の3要素を持つため「同一性」の定義が曖昧であり、このproposalのスコープ外と明確に線引きされた。関連issue#71388では
maps.Equalにも同一ポインタ検査による O(1) 最適化を追加する案が挙がっている