Go has never shipped a set type, and every codebase of any size has written the same map[string]struct{} helper to make up for it. That may end with Go 1.28. An umbrella proposal filed by Alan Donovan on July twenty eighth gathers the work of a collections working group formed in late 2025, and it sketches a coherent family: container/set for comparable elements, container/hash for custom hash and equality, container/ordered backed by balanced trees, and a generic container/heap/v2. The foundation already landed, since the maphash.Hasher interface is part of Go 1.27. Nothing here is accepted yet, and the design choices are more interesting than the package list.
The short answer
An umbrella proposal filed on July twenty eighth by Alan Donovan gathers the output of a Go collections working group formed in late 2025. It covers container/set for comparable elements, container/hash with Map and Set types driven by a custom hasher, container/ordered backed by balanced binary trees, container/mapset helpers for legacy map based sets, and a generic container/heap/v2. The enabling piece, the maphash.Hasher interface, already shipped in Go 1.27. The design leans on transparency: set.Set is defined in terms of map[E]struct so that len, indexing and range keep working. None of it is accepted yet.
Ask a room of Go developers what they have written most often and somebody will say a set. Not a clever one. The four line map[string]struct{} with an Add and a Contains, written again in every service because copying it was always cheaper than agreeing on a library. Generics arrived in Go 1.18 and did not end that, because a generic wrapper still reads worse than the built in map it wraps.
What is actually proposed
The umbrella issue, filed on July twenty eighth by Alan Donovan, is a map of several concrete proposals rather than one API. The pieces:
container/set.Set[T] is the canonical set for comparable elements. container/hash provides Map[K,V] and Set[T] for element types that need a custom hash function and equivalence relation. container/ordered.Map[K,V] is a mapping backed by a balanced binary tree, so iteration follows key order. container/mapset is a set of helper functions for sets written the old way. container/heap/v2.Heap is the generic redesign of the heap API.
Underneath all of it sits hash/maphash.Hasher, which is not a proposal at all any more: it shipped in Go 1.27, the release due this month.
The working group behind this formed in late 2025 and reads like a list of the people who have shaped the language: Jonathan Amsterdam, Alan Donovan, Robert Griesemer, Daniel Martí, Roger Peppe, Keith Randall and Ian Lance Taylor.
The transparency decision is the interesting one
The recommended set.Set is defined transparently in terms of map[E]struct{}.
That single choice explains most of the API. Because the underlying type is a map, len(s) works. Element access works. range works. You are not calling s.Len() and s.Iterate() on a wrapper that exists only to hide a map you can already reason about.
The cost is real and worth naming: the representation is part of the public API, so it can never change. That is a permanent constraint accepted in exchange for the type behaving like something Go developers already know. For a standard library that has to live forever, familiarity is usually the better bet, and this is the same instinct that kept error an interface with one method.
Hashers, and the type you do not control
maphash.Hasher[T] is what makes container/hash possible. The interface bundles two operations: Hash(hash *maphash.Hash, value T) mixes a value into a running hash, and Equal(T, T) bool decides whether two values are the same. ComparableHasher covers the ordinary case where == already means what you want.
The canonical example is case insensitive strings. Your Hash writes the lowercased string into the running hash, your Equal compares the lowercased forms, and a container/hash.Set[string] built with that hasher treats Bob and bob as one element. Nothing in the collection needs to know why.
This is the piece that unlocks the rest, and it is the reason the timing works: with Hasher in 1.27, the collection proposals for 1.28 have a foundation to stand on instead of each inventing their own hashing convention.
API details that suggest the group has written this code before
Two conventions in the proposal stand out.
Mutation methods report whether they changed the size of the collection. That is the difference between s.Add(x) as a statement and if s.Add(x) { ... } as a deduplication check, and it removes the contains-then-add pattern that costs two lookups.
Set operations come in both functional and mutating variants. Union, Intersection and Difference can either return a new set or modify the receiver, because both are legitimate and choosing one forces avoidable allocation on half the callers.
The stated approach to implementation is deliberately plain: satisfy the API and the asymptotic expectations as simply as possible first. Nobody is shipping a hand tuned Swiss table in the first version, and that is the right order.
What to actually do about it
Nothing yet, and that is the point of saying so clearly. This is an open proposal with a milestone, not a release note. Go 1.28 lands in early 2027 if the usual cadence holds, and the individual sub proposals still have to clear review on their own.
What is worth doing today is reading issue 80590 and the sub issues while comments still matter, particularly if your codebase has one of those hand rolled set libraries with strong opinions in it. The working group is explicitly asking for feedback on a design that, once it ships, cannot be changed.
Sources and further reading
- Go proposal: container/... generic collection types (issue 80590)
- Go proposal: container/set, a generic set type (issue 69230)
- Go proposal: container/hash.Map with custom hash function (issue 69559)
- Anton Zhiyanov: Go proposal, Hashers
- Go 1.27 release notes
- hash/maphash package documentation
Frequently asked questions
Is this accepted, and when could it actually ship?
It is not accepted. What exists is an umbrella proposal, golang/go issue 80590, filed on July twenty eighth, 2026 and carrying a Go 1.28 milestone, which gathers several concrete sub proposals that each go through the normal review process. Some of those are older than the umbrella: the container/set proposal is issue 69230 and the container/hash Map proposal is issue 69559, both open since 2024. A milestone on a Go proposal is a target rather than a commitment. Go 1.28 would land in early 2027 on the project's usual six month cadence, and a plausible outcome is that some of this family arrives then and some slips or changes shape after community review.
Why define set.Set in terms of map[E]struct{} instead of hiding the representation?
Because the transparent definition is what gets you the language features for free. A set that is defined as map[E]struct{} supports len(set), direct element access and range iteration using the built in syntax, rather than requiring a method for each. That is the ergonomic gap generics alone did not close: a wrapper type with a Len() method reads worse than len(s) and composes worse with the rest of the language. The cost is that the representation is part of the API, so it cannot change later. The working group appears to have decided that predictable, familiar behaviour is worth more than the freedom to swap the implementation, which is a very Go trade.
What is maphash.Hasher and why does the rest depend on it?
It is the interface that lets a collection hash and compare a type it does not control. The shape is small: a Hasher[T] provides Hash(hash *maphash.Hash, value T) to mix a value into a running hash, and Equal(T, T) bool to compare two values. A ComparableHasher covers types that already work with ordinary equality. It shipped in Go 1.27, the release due this month, which is why the collections work can be proposed now. The classic use is a case insensitive string set: your Hash lowercases before writing, your Equal lowercases before comparing, and container/hash builds a set with those semantics without knowing anything about your type.
What does container/ordered give me that a map does not?
Order. A Go map has no defined iteration order, deliberately, and the standard fix is to collect keys into a slice and sort them every time you need to walk in order. container/ordered.Map is proposed as a mapping backed by a balanced binary tree, which keeps elements in key order at all times. That changes the asymptotics of a common pattern: instead of an O(n log n) sort per traversal, you pay O(log n) on insertion and get ordered iteration for free. It also makes range queries expressible, which a hash map fundamentally cannot do. The trade is slower point lookups than a hash map, which is the normal tree versus hash table decision.
Do the existing container packages go away?
No, and the proposal is careful about that. The heap work is proposed as container/heap/v2, a versioned path rather than a replacement in place, which is the mechanism the standard library uses when an API needs a generic redesign without breaking code that already imports the old one. There is also a proposed container/mapset with helper functions for sets written the legacy way, as map[E]bool or map[E]struct{}, which is a migration aid rather than a new data structure. Go's compatibility promise means nothing in the standard library is removed, so the realistic outcome is that old and new coexist and new code reaches for the new packages.