メインコンテンツへスキップ
#80590likely_accept

container/...: generic collection types

ステータス変更: active likely_accept

要約

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

概要

Go 1.18 のジェネリクスと Go 1.23 のイテレータを前提に、集合(Set)、順序付きマップ、カスタムハッシュを使うマップ、ヒープなどのコレクション型をまとめて追加するための「傘(umbrella)proposal」です。Go Collections ワーキンググループ(@jba, @adonovan, @griesemer, @mvdan, @rogpeppe, @khr, @ianlancetaylor)が提出しました。最初の置き場所は標準ライブラリではなく golang.org/x/exp/collection/... です。

ステータス変更

active → likely_accept
@aclements は2026年10月29日、「golang.org/x/exp に一連のコレクションパッケージを追加し、実際に使った経験から学んだうえで最終的に標準ライブラリへ取り込む」という長期方針の傘 proposal として likely accept を表明しました。同じ週には、以下の個別 proposal もそろって likely accept になっています。

  • hashed.Map(#69559)
  • hashed.Set(#80584)
  • set.Set(#69230)
  • sorted.Map/Set(#60630)
    10月7日のレビューでは、判断すべき点が3つに整理されていました。
  • そもそもやるべきか(答えはほぼ確実に yes)
  • 置き場所はどこか(まずは x/exp/collection が妥当)
  • API はおおむね正しいか(std へ移すときに変更できるが、大きく変わらないことが望ましい)
    懸案だった JSON の扱いも本文に明記されました。これで残る論点がなくなったと考えられます。

技術的背景

現状の問題点

  • 標準ライブラリにはコレクション型がほとんどありません。Set すら存在せず、慣習的に map[T]bool や map[T]struct{} で代用されています。map[T]bool の場合は「値が false の要素は集合に含まれるのか」が曖昧になります。
  • 二分木などを使う順序付きのマップや集合がありません。範囲検索(range query)が必要な場面では、「map を作ってからキーをソートする」という定番パターンでは性能が足りません。
  • スライスや go/types.Type のように、== で比較できない、または比較結果が意図と合わない型をキーにしたハッシュマップを作れません。
  • 既存の container/heap はジェネリクス以前の設計なので、使いにくいままです。

提案された解決策

個別 proposal へのリンク集として、次のパッケージを x/exp に追加します。

  • hash/maphash.Hasher(#70471、Go 1.27 でリリース済み): 任意の型に独自のハッシュ関数と等価関係を定義するためのインターフェース
  • collection/hashed.Map[K,V] と hashed.Set[T]: 上記の Hasher を使うハッシュマップと集合
  • collection/set.Set[T]: 実体は map[T]struct{} そのもので、range でそのまま回せます。新しい API で使う「標準の集合型」になることが期待されています。
  • collection/sorted.Map[K,V] と sorted.Set[T]: キーの順序に基づくマップと集合。現在の実装は平衡二分木ですが、設計上は実装方式を固定していません。
  • collection/heap.Heap(#77397)と挿入順を保つマップ(#80194): 議論が続いており、どちらもまだ likely accept になっていません。
    API 全体で共通する規約も決められました。
  • 変更系のメソッドは、長さが変わったかどうかを bool で返します。
  • Get(K) V と Lookup(K) (V, bool) を使い分けます。
  • Union は新しい集合を返します。レシーバ側を書き換える版として UnionWith を別に用意します。
  • DeleteFunc を提供します。
    共通の抽象インターフェース(F-bounded な制約型)は非公開とし、今回の提案には含めません。
    JSON の扱いは次のとおりです。
    型 JSON 上の表現
    マップ 組み込み map と同じ形式
    集合 JSON 配列。json/v2 の Deterministic オプションを尊重する
    sorted 系 常にソート順で出力
    Heap 組み込みのマーシャリングなし

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

  • 自前の Set 実装や map[T]bool をやめて、和・積・差などの集合演算を型安全に書けます。
  • sorted を使えば、「ある値以上の要素を順に処理する」といった範囲走査や、最小値・最大値の取得を効率よく行えます。
  • hashed.Map を使えば、types.Type のように特殊な等価判定が必要な値をキーにできます。静的解析ツールの開発などで役立ちます。
  • x/exp にあるため、古い Go のバージョンでも使えます。

コード例

// Before: map で集合を代用する
a := map[int]bool{1: true, 2: true, 3: true}
b := map[int]bool{3: true, 4: true, 5: true}
inter := map[int]bool{}
for k := range a {
	if b[k] {
		inter[k] = true
	}
}
// After: golang.org/x/exp/collection/set
x := set.Of(1, 2, 3)
y := set.Of(3, 4, 5)
z := x.Intersection(y) // {3}(新しい集合を返す)
x.UnionWith(y)         // x 自身を書き換える版
for e := range z {     // 実体は map[E]struct{} なので range でそのまま回せる
	println(e)
}

議論のハイライト

  • パッケージ名: 最も議論が長引きました。container/maps は既存の maps パッケージと名前が衝突するとして @neild らが強く反対しました。最終的に、親ディレクトリは Linux のコンテナと紛らわしくない collection とし、その下に hashed / sorted / set を置く案に落ち着きました。sorted は実装方式(B-tree など)を名前で固定しないために選ばれています。
  • 配置場所: いきなり std に入れず、まず x/exp で実際に使ってもらってフィードバックを集める方針に変わりました。Go 1.29 で std 入りを再提案する予定です。
  • 抽象インターフェース: Java のような統一的なコレクション階層は目指さないと明言されました。インターフェースは非公開とし、Go では利用者が必要な分だけ制約を自分で定義すればよいという立場です。
  • API の対象範囲: 遅延処理型や並行処理型のデータ構造では、bool の戻り値や Len を実装しにくいという指摘がありました(@aclements)。これに対し API は「メモリ上で即時に処理する素直な実装」向けと割り切っています。
  • 削除されたメソッド: ContainsAll、DeleteAll、SetAll は意味が曖昧で、性能面の利点もないとして外されました。代わりにイテレータ側のメソッドを提案しています(#81382)。

関連リンク