Go got generics in 2022 and still has no set type. Proposal #80590, opened July 28 by the Collections Working Group, is the fix: container/set, container/hash, container/ordered and a container/heap/v2, all targeting Go 1.28. Implementation CLs are already written. The four-year delay was not neglect, and understanding why it took this long explains most of the design decisions in the proposal.
The working group formed in late 2025 and reads like a roll call of the people who built the language: Jonathan Amsterdam, Alan Donovan, Robert Griesemer, Daniel Martí, Roger Peppe, Keith Randall and Ian Lance Taylor.
RelatedWindows 11 Tests Cloud Rebuild, a No-USB Recovery Tool
What is actually being proposed
Six pieces, and they are not interchangeable:
| Package | Provides | Backed by | Use when |
|---|---|---|---|
| container/set | Set[T] | map[T]struct{} | Elements are comparable |
| container/hash | Map[K,V], Set[T] | Custom hash functions | Keys are not comparable, or need custom equality |
| container/ordered | Map[K,V] | Balanced binary trees | Iteration order matters |
| container/heap/v2 | Generic heap | Binary heap | Replacing the interface-based heap |
| container/mapset | Helper functions | Existing maps | Legacy map-as-set code |
| hash/maphash | Hash/equivalence interfaces | Go 1.27 | Already shipped, underpins container/hash |
The split between container/set and container/hash is the practical one most code will care about. If your elements are comparable, you get the cheap map-backed set. If they are not, or you need equality that differs from ==, you supply a hash function and use the other package. Go 1.27 already shipped the hash/maphash interfaces this depends on, which is why the proposal can arrive with working code rather than a sketch.
Why did this take four years?
Generics gave Go the type machinery for a set in 2022. Writing one is a weekend project, and the ecosystem produced dozens. Standardizing one is a different problem, and the proposal is unusually candid about the obstacle: the binary method problem.
Consider a Union method. On a hash-backed set it takes another hash-backed set. On a tree-backed set it takes a tree-backed set. Both are "a set with a Union method," but there is no common interface either can satisfy, because the argument type varies with the receiver type. Any interface you write pins down the argument, and pinning it down is exactly what you cannot do. The working group addresses this with F-bounded polymorphism, the same technique that makes Comparable<T> work in other languages, and it produces type signatures that are harder to read than most of the standard library.
Their response to that difficulty is the most interesting decision in the document: the abstract Collection, Set and Map interfaces exist in the implementation but stay unexported. Concrete types ship, the abstractions that unify them do not, pending community feedback. That is a deliberate bet that a wrong interface is more expensive than a missing one, which is correct under Go's compatibility promise. An exported interface is permanent.
The API choices worth arguing about
Two decisions will generate most of the discussion on the issue.
Mutation methods return information rather than nothing. Set and Delete report the previous value and whether the collection's size changed. It makes the common check-then-act pattern a single operation instead of two lookups, at the cost of a signature that is noisier than Go usually tolerates.
Set algebra ships twice. There are functional methods that allocate a new set, such as Union, and mutating variants suffixed with -With that modify the receiver. Two spellings of one concept is the kind of surface area Go normally refuses. The justification is performance: in a hot loop, allocating a fresh set per union is the entire cost.
RelatedGitHub Ships Native Stacked Pull Requests in Preview
Intersection and Union also sit in the interfaces rather than being derived from iteration, so implementations can optimize them. A tree-backed set can intersect two sorted structures far faster than element-by-element membership testing, and an interface that only exposed iteration would forfeit that.
Our take
Most Go teams will change roughly one thing: delete a hand-rolled map[string]struct{} helper and import container/set. That is a small win repeated in an enormous number of codebases, and it removes a recurring source of subtle inconsistency, since every team's homegrown set has slightly different semantics for delete-on-missing and iteration order.
The larger effect is on libraries. Public APIs that currently accept map[T]struct{} or a bespoke set type have no shared vocabulary today, so every library boundary needs a conversion. A standard-library set fixes that, and it fixes it only once the ecosystem migrates, which will take longer than the release itself.
Holding the interfaces back is the right call even though it will frustrate people who want to write code generic over any set. Go's compatibility guarantee means an exported interface is a permanent commitment, and the binary method problem is exactly the kind of thing that looks solved until three implementations exist.
- Whether the interfaces get exported. The working group is explicitly waiting for feedback. If they ship unexported, expect a follow-up proposal within two releases.
- The -With naming. Dual mutating and functional APIs are unusual for Go. This is the most likely thing to change before it lands.
- heap/v2 adoption. The first v2 of an existing container package sets the precedent for how Go modernizes the rest of the standard library.
- Officialgolang/go#80590: proposal for generic collections full design, opened July 28, 2026
- ReferenceBruce et al., "On Binary Methods" the type-theory problem the proposal works around
- OfficialThe Go Blog release notes and proposal outcomes
Original analysis by GenZTech, based on the public proposal thread.
