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

container/...: generic collection types

新規提案

要約

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

概要

Go 1.18 のジェネリクスと Go 1.23 のイテレータを土台に、セット、ハッシュマップ、ソート済みマップ、ヒープといった汎用コレクション型をまとめて導入するための「アンブレラ(傘)proposal」です。2025年末に発足した Go Collections ワーキンググループ(@jba, @adonovan, @griesemer, @mvdan, @rogpeppe, @khr, @ianlancetaylor)が提出しました。個々の型は別の proposal で扱い、この issue では全体方針と API の共通規約を議論します。

ステータス変更

(新規) → active
2026年10月7日のレビュー会議で、@aclements が次の3点を検討事項として挙げ、Active 列に追加されました。

  • そもそもこれをやるべきか(「ほぼ確実に yes」とのこと)
  • パッケージをどこに置くか(まずは x/exp/collection が良さそう)
  • API の大枠は妥当か
    当初は Go 1.28 で標準ライブラリに入れる案でしたが、コミュニティの意見を受けて方針が変わりました。まず golang.org/x/exp で実際に使ってもらい、約半年後(Go 1.29 の時期)に標準ライブラリ入りを改めて提案します。この変更で受け入れのハードルが下がったことが、Active 化の後押しになったと考えられます。

技術的背景

現状の問題点

Go 標準のコレクションは事実上スライスと map だけです。セットすらなく、map[T]bool か map[T]struct{} で代用するのが慣習でした。map[T]bool では「false の値が入っている」ケースが紛らわしくなります。ほかにも次のような不足がありました。

  • 二分木などで実装された順序付きマップがない
  • スライスや types.Type のような比較できないキー、または独自の等価判定が必要なキーを扱えるハッシュマップがない
  • 既存の container/heap は使いにくい

提案された解決策

個別 proposal として次の型が挙げられています(パッケージ名は議論中)。

  • hash/maphash.Hasher(#70471、Go 1.27 で導入済み): 独自のハッシュ関数と等価関係を表すインターフェース
  • hashed.Map[K,V](#69559)と hashed.Set[T](#80584): Hasher を使うハッシュテーブル
  • set.Set[T](#69230): 中身は map[T]struct{} そのもので、Union や Intersection などの集合演算を持つ「標準のセット」
  • sorted.Map[K,V] と sorted.Set(#60630): キー順に並ぶマップとセット。実装はバランス木ですが、名前で実装を縛らない方針です
  • heap.Heap(#77397): 型パラメータを持つヒープ。既存の heap を置き換えます
    全型に共通する API 規約もあります。
  • 値の取得は Get(K) V と Lookup(K) (V, bool)
  • Set と Delete は以前の値と存在フラグを返す
  • 集合演算には、新しいセットを返す Union と、左辺を書き換える UnionWith の2種類を用意する
    共通の抽象インターフェース(F-bounded な制約型、つまり自分自身の型を型パラメータに取る制約)は非公開のままで、今回の proposal には含まれません。

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

  • 重複除去や権限チェックなどで、型安全なセットを集合演算付きで使える
  • 範囲検索やソート順での走査が必要な場面(時系列インデックス、リーダーボードなど)で、毎回キーをソートせずに済む
  • go/types の型のように「同一性を独自に定義するキー」を使ってハッシュマップを作れる
  • 型パラメータを持つヒープで、優先度付きキューを簡単に書ける

コード例

// Before: map[T]struct{} で集合演算を手書きする
a := map[string]struct{}{"read": {}, "write": {}}
b := map[string]struct{}{"write": {}, "admin": {}}
union := make(map[string]struct{})
for k := range a { union[k] = struct{}{} }
for k := range b { union[k] = struct{}{} }
// After: set.Set を使う(API は x/exp での検証中のため、今後変わる可能性があります)
a := set.Set[string]{}
a.Insert("read")       // 要素数が変わったかを bool で返す
u := a.Union(b)        // 新しいセットを返す
a.UnionWith(b)         // a を書き換える(アロケーションを抑えられる)
// 順序付きマップ
m := sorted.Map[int, string]{} // 生成方法は個別 proposal で議論中
m.Set(3, "c")
v, ok := m.Lookup(3)

議論のハイライト

  • パッケージ名で最も議論が分かれた。 container/{sets,maps} 案は既存の maps パッケージと名前が衝突するとして @neild らが強く反対しました。@jba が Rust、C++、Java、Swift の命名を比較したうえで hashed.Map / sorted.Map 案に戻しています。また、Linux のコンテナと紛らわしいことと、既存の container/* と区別するため、親パッケージ名は collection にする方向です。
  • 変更の有無を返す bool の扱い。 @aclements は、遅延評価型や並行データ構造ではこの bool を返しにくいと懸念しました。ワーキンググループは「API はメモリ上で即時に処理する素直な実装向けに設計する」と判断しています。
  • 削除されたメソッドと名前の変更。 ContainsAll / DeleteAll / SetAll は意味が曖昧なうえ、ループで書けるため削除されました。代わりにイテレータ側のメソッドを別途提案しています(#81382)。At / Get はそれぞれ Get / Lookup に改名されました。
  • 抽象インターフェースは公開しない。 Java のような統一的なインターフェース体系は目指さず、実際の利用経験を積んでから公開するかどうかを決めます。マップを「ペアのコレクション」とみなす案も見送られました。
  • JSON 対応は未決。 マップは encoding/json の map と同じ扱いにし、セットは JSON 配列にする案が出ています。@adonovan は「後から形式を変えると多くのプロジェクトが壊れるので早く決めるべき」と指摘しました。@Merovius は、順序のないセットを JSON 配列に出すと出力が実行ごとに変わり得る点を挙げています。

関連リンク