Go Proposal: `maphash.Hasher` — Standardizing Hash and Equality Interfaces.
Go 1.26 will introduce a new interface, maphash.Hasher[T], to provide a uniform standard for hashing and equality judgments. It allows…
Go 1.26 will introduce a new interface, maphash.Hasher[T], to provide a uniform standard for hashing and equality judgments. It allows developers to define generically safe hashing logic, supports random seed collision prevention, and simplifies collection library implementations. It is expected to become the core foundation for future Go containers and generalized collections.
In the Go 1.26 roadmap, a low-profile yet foundational proposal has been accepted: introducing a standard interface Hasher[T] for custom hashing and equality checks, defined in the hash/maphash package.
The related issue is hash: standardize the hash function #70471, which discusses providing a unified hashing/equality mechanism for the Go ecosystem.
This article starts with the background, explains the Hasher[T] interface and its motivation, provides practical examples, and explores the potential ecosystem impact.
🧭 Why Standardize Hash and Equality Interfaces?
Pain Point: Fragmented Custom Hashing Code
In the Go community, many libraries and frameworks — especially those implementing custom collections, concurrent hash structures, or generic containers — require writing hash functions and equality checks for specific types. The current landscape is chaotic: each library has its own signature and style, using interface{}, returning uint64, with or without seeds, making interoperability difficult.
While Go’s maphash package provides seeded hashing for []byte, string, and comparable types via maphash.Bytes, maphash.String, and maphash.Comparable (pkg.go.dev), it lacks a standard interface for arbitrary types, especially user-defined ones.
Anton noted in his “Accepted! Go proposals distilled” series that the new Hasher[T] interface will become "the standard way to hash and compare elements in custom collections or map/set implementations" (antonz.org).
In short, this proposal builds a unified infrastructure for “hash + equality” in the Go ecosystem, reducing wheel reinvention and promoting library compatibility.
📜 What is maphash.Hasher[T]?
The proposal defines the interface as:
type Hasher[T any] interface {
// Hash writes value's hash content to the given *maphash.Hash
// If Equal(a, b) is true, their Hash results must be identical
Hash(hash *maphash.Hash, value T)
Equal(a, b T) bool
}For comparable types, a default implementation ComparableHasher[T comparable] is provided, where Equal(x, y) = x == y and Hash uses maphash.WriteComparable internally.
Example: Case-Insensitive String Hasher
type CaseInsensitive struct{}
func (CaseInsensitive) Hash(h *maphash.Hash, s string) {
h.WriteString(strings.ToLower(s))
}
func (CaseInsensitive) Equal(a, b string) bool {
return strings.ToLower(a) == strings.ToLower(b)
}This demonstrates that you can fully customize your “equality + hashing” logic (e.g., case-insensitive, ignoring fields).
Here’s a generic Set implementation:
type Set[H maphash.Hasher[V], V any] struct {
seed maphash.Seed
hasher H
data map[uint64][]V
}
func NewSet[H maphash.Hasher[V], V any](hasher H) *Set[H, V] {
return &Set[H, V]{
seed: maphash.MakeSeed(),
hasher: hasher,
data: make(map[uint64][]V),
}
}
// Calculate hash value for v
func (s *Set[H, V]) calcHash(v V) uint64 {
var h maphash.Hash
h.SetSeed(s.seed)
s.hasher.Hash(&h, v)
return h.Sum64()
}This Set uses linear probing (via the Equal method) to resolve hash collisions. The example clearly illustrates the Hasher interface's purpose.
🧩 Real-World Examples and Community Usage
3.1 Existing Libraries with Custom Hashers
Many community libraries implement their own hashing logic, especially in data structures, generic containers, and caching libraries. These implementations vary widely in signature and style:

github.com/cornelk/hashmapgithub.com/dolthub/maphashgithub.com/emirpasic/godsgithub.com/deckarep/golang-setgithub.com/zyedidia/genericgolang.org/x/exp/maps
Below are interesting examples from the community/blogs that deepen understanding of this interface and its transformative value.
3.2 Using maphash.WriteComparable in Custom Hashing
In Matt Proud’s blog “How I learned to love package maphash,” he shows how to write a hash method for a complex struct (TheZoo), using maphash.WriteComparable for basic comparable fields and writing length/order info for slices/maps to generate reasonable hash values (matttproud.com).
Example:
func writeHashTheZoo(h *maphash.Hash, zoo *TheZoo) {
if zoo == nil {
maphash.WriteComparable(h, 0)
return
}
maphash.WriteComparable(h, 1)
// Write ID, Optional, Unordered, Variable fields sequentially
maphash.WriteComparable(h, zoo.ID)
// For maps/slices: write length first, then each element
maphash.WriteComparable(h, len(zoo.Unordered))
for _, k := range slices.Sorted(maps.Keys(zoo.Unordered)) {
maphash.WriteComparable(h, k)
maphash.WriteComparable(h, zoo.Unordered[k])
}
// Recursive calls, etc.
}He proposes a convention: writeHashX functions handle writing to the hash stream, while optional hashX(seed, v) functions encapsulate the maphash.Hash usage (matttproud.com).
This example demonstrates that even before the standard Hasher[T] interface was introduced, we were writing similar hash functions. With a standard interface, we can integrate these practices into a more unified, structured system.
3.3 Existing Support for maphash.Comparable / maphash.Hashable
The maphash package already supports the maphash.Comparable function for hashing comparable types with a seed parameter (pkg.go.dev). In issue #54670, there was a proposal to add Comparable support: func Comparable[T comparable](seed Seed, v T) uint64. This proposal has been accepted.
This means we already have a mechanism for seed-driven hashing of basic types, even before widespread Hasher[T] adoption.
3.4 Using Hasher in Generic Containers / Custom Maps
In Go’s issue tracker, there’s a higher-level proposal — container/hash: Map (Issue #69559)—hoping to provide a generic Map with custom hash/equality in the standard library or x/exp. If adopted, it would likely use maphash.Hasher[K] as the base interface (GitHub).
Draft excerpt:
package hash
type Map[K, V any, H maphash.Hasher[K]] struct { … }
func NewMap[K, V any, H maphash.Hasher[K]]() *Map[K, V, H]This means the Hasher interface could become part of the future standard container ecosystem.
3.5 Writing Your Own Set/Map with Hasher
Here’s a generic Set implementation skeleton (integrating Anton's Set with my own comments), showing how to use Hasher:
type Set[H maphash.Hasher[V], V any] struct {
seed maphash.Seed
hasher H
buckets map[uint64][]V
}
func NewSet[H maphash.Hasher[V], V any](hasher H) *Set[H, V] {
return &Set[H, V]{
seed: maphash.MakeSeed(),
hasher: hasher,
buckets: make(map[uint64][]V),
}
}
func (s *Set[H, V]) hashOf(v V) uint64 {
var h maphash.Hash
h.SetSeed(s.seed)
s.hasher.Hash(&h, v)
return h.Sum64()
}
func (s *Set[H, V]) Add(v V) {
hv := s.hashOf(v)
bucket := s.buckets[hv]
for _, existing := range bucket {
if s.hasher.Equal(existing, v) {
return
}
}
s.buckets[hv] = append(bucket, v)
}
func (s *Set[H, V]) Contains(v V) bool {
hv := s.hashOf(v)
for _, existing := range s.buckets[hv] {
if s.hasher.Equal(existing, v) {
return true
}
}
return false
}
func (s *Set[H, V]) Delete(v V) {
hv := s.hashOf(v)
bucket := s.buckets[hv]
newb := bucket[:0]
for _, existing := range bucket {
if !s.hasher.Equal(existing, v) {
newb = append(newb, existing)
}
}
if len(newb) > 0 {
s.buckets[hv] = newb
} else {
delete(s.buckets, hv)
}
}Further optimizations (rehashing, resizing, bucket chain length control) would follow standard hash table implementation patterns.
For users, using Set[ComparableHasher[T], T] or your custom CaseInsensitiveStringHasher becomes very intuitive.
🚀 Impact, Challenges, and Outlook
4.1 Impact
This proposal has several long-term values for the Go ecosystem:
- Unified standard, better library interoperability
Once different container/collection libraries adopt theHasherinterface, they can interoperate more easily: hash logic written for one library can be reused in another without modification. - Lower implementation difficulty and error rate
Users don’t need to figure out theHash+Eqcontract relationship (Equal(x, y) ⇒ Hash(x) == Hash(y)) each time. Library authors can validateHasherinterface requirements in documentation and tests. - Enhanced security
Usingmaphash.Seed-driven hashing makes hash behavior unpredictable, defending against hash collision attacks (hash flooding). - Foundation for standard containers/collections
If the standard library wants to support genericMap[K, V, H Hasher[K]]or provide more flexible collection packages in the future, this interface is the proper foundation, as indicated by thecontainer/hash: Mapproposal (GitHub).
4.2 Challenges and Considerations
While the interface design is attractive, implementation and adoption present several considerations:
- Performance overhead
In performance-sensitive scenarios, callingHasher.Hash+maphash.Hashwrites might be slower than direct inline hashing. Library authors must weigh whether it's worth it in hot paths. - Zero value / empty interface design
The interface should be designed for “zero value validity” (i.e., the default Hasher is usable). Anton’s article mentions:Hashershould be stateless with a valid zero value (antonz.org) - Adaptation to existing hash functions
Legacy libraries might usefunc(T) uint64or other signatures for hashing. Providing adapter layers (wrappers) or gradual migration mechanisms is a challenging task. - Design for complex type hashing
For structs, slices, maps, pointers, and mutable fields, designing “reasonable” hash + equality logic still has design space. The community needs to gradually form best practices. - Adoption by standard library / x/exp container packages
If future container libraries / standard libraries don’t adopt it, user usage of this interface might become isolated. Thus, the interface’s value primarily depends on ecosystem adoption.
🔚 Conclusion
maphash.Hasher[T] is a step forward for Go in hash + equality infrastructure. It's not a "flashy language feature," but it lays the foundation for future evolution of containers, collections, and hash structures. Combined with the earlier hash: standardize the hash function proposal, the roadmap becomes increasingly clear: the Go ecosystem is evolving toward "standardization, unification, and extensibility."
In the future, when you use a third-party Set/Map library, you might write:
s := NewSet[ComparableHasher[MyType], MyType](ComparableHasher[MyType]{})Or use your custom CaseInsensitiveStringHasher, UserIDHasher, etc., without invading the library internals.