golang.org/x/exp/collection/set: new package with a generic set type
要約
概要
Goで、比較可能な(comparable)要素を持つ集合を表す汎用型 Set[E] を、実験用モジュール golang.org/x/exp/collection/set として追加する提案です。目標は、新しく作るGoのAPIで集合を表すときの「標準の型」を決めることです。
ステータス変更
(新規) → active
この提案は2024年9月に出され、長い間コミュニティの議論だけが続いていました。2025年末にGoチームの「Collections working group」が作られました。メンバーは @jba、@adonovan、@griesemer、@mvdan、@rogpeppe、@khr、@ianlancetaylor です。2026年7月、このグループは推奨APIをまとめ、コレクション型全体をまとめる親issue #80590 の一部としてこの提案を位置づけました。
その後APIは何度か直されました。最後の変更(2026年10月7日)で、置き場所は golang.org/x/exp/collection/set、関数名は Of と Collect に戻りました。実装CL 845385 もできています。これを受けて、2026年10月8日に @aclements がactive列に移し、毎週のproposal review meetingで正式に審議されることになりました。
技術的背景
現状の問題点
Goの標準ライブラリには集合型がありません。そのため、普通は map[T]bool か map[T]struct{} で代用しています。この方法には次の問題があります。
- 和集合・積集合・差集合などを求めるたびに、
makeとループを手で書く必要がある map[T]boolでは、値がfalseのキーを「集合に含まれる」とみなすかどうかがあいまいになる- 公開APIで集合を受け渡すときの共通の型がなく、
golang-set、hashicorp/go-setなどのライブラリがそれぞれ別の型を使っている
提案された解決策
最新のAPIは、型を map そのものとして定義しています。
package set // import "golang.org/x/exp/collection/set"
type Set[E comparable] map[E]struct{}
func Of[E comparable](elems ...E) Set[E]
func Collect[E comparable](seq iter.Seq[E]) Set[E]
func (x Set[E]) All() iter.Seq[E]
func (s Set[E]) Clear()
func (s Set[E]) Clone() Set[E]
func (x Set[E]) Contains(e E) bool
func (x Set[E]) Delete(e E) bool
func (x Set[E]) DeleteFunc(f func(E) bool) bool
func (x Set[E]) Equal(y Set[E]) bool
func (x Set[E]) Insert(elem E) bool
func (x Set[E]) Intersects(y Set[E]) bool
func (x Set[E]) Len() int
func (x Set[E]) String() string // "{a, ..., z}" 形式。要素はソートされる
func (x Set[E]) Union(y Set[E]) Set[E]
func (x Set[E]) UnionWith(y Set[E])
func (x Set[E]) Intersection(y Set[E]) Set[E]
func (x Set[E]) IntersectionWith(y Set[E])
func (x Set[E]) Difference(y Set[E]) Set[E]
func (x Set[E]) DifferenceWith(y Set[E])
func (x Set[E]) SymmetricDifference(y Set[E]) Set[E]
func (x Set[E]) SymmetricDifferenceWith(y Set[E])
- 演算は2種類あります。
Unionのように新しい集合を返す版と、UnionWithのようにレシーバ自身を書き換える版です。 InsertとDeleteは、集合の要素数が変わったかどうかをboolで返します。
これによって何ができるようになるか
中身は map[E]struct{} なので、len(s)、_, ok := s[e]、for e := range s といったmapの操作がそのまま使えます。一方で、Len、Contains、All といったメソッドもあります。これらは組み込み操作と重複して見えますが、抽象的な集合を受け取るジェネリック関数にこの型を渡せるようにするためのものです。
想定される主な使い道は次の3つです。
- 2つのスライスに共通する要素を探す処理を、
slices.Containsによる O(n*m) の二重ループではなく、集合演算で書ける - 公開APIの引数や戻り値で「重複のない集まり」を型として示せる
slices.Valuesやmaps.Keysで得たイテレータから、Collectで集合を作れる
コード例
// Before: map を集合として手書きで扱う
a := map[string]struct{}{"x": {}, "y": {}}
b := map[string]struct{}{"y": {}, "z": {}}
inter := make(map[string]struct{})
for k := range a {
if _, ok := b[k]; ok {
inter[k] = struct{}{}
}
}
// After: set パッケージを使う
x := set.Of("x", "y")
y := set.Collect(slices.Values([]string{"y", "z"}))
z := x.Intersection(y) // {y}
fmt.Println(z) // "{y}"
for e := range z { // map と同じく range で回せる
println(e)
}
議論のハイライト
- struct型かmap型か: @ianlancetaylor の当初案は、map をラップする
structを作り、ポインタレシーバのメソッドを持たせるものでした。working groupは最終的にmap[E]struct{}をそのまま型として定義する方式を選びました。理由は、mapと同じ参照の振る舞いで分かりやすく、rangeなどの組み込み操作も使えるためです。 - メソッドの削減:
ContainsAll、InsertAll、DeleteAllは削除されました。ContainsFuncも入れていません。@adonovan によると、イテレータとforループで書いても計算量は変わらないためです。一方DeleteFuncは、走査と削除をまとめて効率よく行える可能性があるため残されました。 - 置き場所と名前の変遷:
container/set→golang.org/x/container/sets(関数名はSetOf/CollectSetに変更)→golang.org/x/exp/collection/set(Of/Collectに戻す)と変わりました。最終的には、正式採用前の試作として x/exp に置く形になりました。 - 関連提案との役割分担:
- #77052
container/mapset: 既存のmap[T]boolなどの「旧来の集合」に対する補助関数 - #80584
hashed.Set: 比較できない要素型や、独自の等価性判定を使う場合のハッシュ集合 - 本提案: 新しいAPIで使う標準の集合型
- #77052
- 却下・保留されたアイデア:
set.Set{...}のような複合リテラル構文(言語仕様の変更が必要になる)、直積(Cartesian product)、InsertIfは採用されていません。