Why Go’s New Hasher[T] Interface Could Revolutionize Custom Collections
The article explains the Go 1.26 proposal introducing a standard Hasher[T] interface for custom hash and equality logic, outlines its definition, showcases practical examples, compares existing library approaches, and discusses the long‑term benefits and challenges for the Go ecosystem.
In the Go 1.26 roadmap, a modest yet foundational proposal was accepted: a standard Hasher[T] interface defined in the hash/maphash package to unify custom hash and equality operations.
🧭 1. Why standardize a hash/equality interface?
Many Go libraries implement their own hash functions and equality checks for custom collections, often using interface{}, returning uint64, or handling seeds inconsistently. The existing maphash package provides seeded hashing for []byte, string, and comparable types, but lacks a generic interface for arbitrary user‑defined types.
Anton’s article notes that the new Hasher[T] interface will become the standard way to hash and compare elements in custom sets or maps.
📜 2. What is maphash.Hasher[T] ?
type Hasher[T any] interface {
// Hash writes the hash of value into the provided *maphash.Hash.
// If Equal(a, b) is true, then Hash(a) must equal Hash(b).
Hash(hash *maphash.Hash, value T)
Equal(a, b T) bool
}A default implementation ComparableHasher[T comparable] is provided, where Equal(x, y) = x == y and Hash uses maphash.WriteComparable. Example of a 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)
}🧩 3. Real‑world examples & community usage
Various Go libraries currently implement their own hash logic (e.g., github.com/cornelk/hashmap, github.com/emirpasic/gods, github.com/deckarep/golang-set), each with different signatures and limitations. The proposal encourages these libraries to adopt the standard Hasher interface for better interoperability.
Matt Proud’s blog demonstrates using maphash.WriteComparable to hash complex structs, following a naming convention like writeHashX that writes fields in a deterministic order.
A generic Set implementation using Hasher illustrates how to compute bucket hashes, resolve collisions with Equal, and perform add, contains, and delete operations:
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)
for _, existing := range s.buckets[hv] {
if s.hasher.Equal(existing, v) {
return
}
}
s.buckets[hv] = append(s.buckets[hv], 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)
}
}🚀 4. Impact, challenges, and outlook
Standardization – Libraries that adopt Hasher can interoperate without rewriting hash logic.
Reduced implementation errors – Users no longer need to manually ensure the contract Equal(a,b) ⇒ Hash(a)==Hash(b).
Security – Seeded hashing mitigates hash‑flooding attacks.
Foundation for generic containers – Future standard library or x/exp containers may use Hasher as the core hashing contract.
Challenges include potential performance overhead compared to inline hashing, ensuring the zero‑value of a hasher is usable, providing migration paths for existing libraries, and achieving broad ecosystem adoption.
5. Conclusion
The maphash.Hasher[T] interface marks a significant step in Go’s evolution toward a unified, extensible hashing infrastructure. While not a flashy language feature, it lays the groundwork for future generic containers and safer, more interoperable libraries.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
