21. Generics: type parameters and constraints
21.1 The problem generics solve
Before Go 1.18, a generic-shaped function like "sum any slice of numbers"
required either code generation, interface{} plus reflection (slow,
unsafe at compile time), or a copy-pasted function per type. Generics add
compile-time-checked type parameters:
func Sum[T int | int64 | float64](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
Sum([]int{1, 2, 3}) // T inferred as int
Sum([]float64{1.5, 2.5}) // T inferred as float64
21.2 Constraints
T int | int64 | float64 is an inline type set constraint. The
standard library's golang.org/x/exp/constraints (and the built-in
cmp.Ordered in modern Go) name common ones:
import "cmp"
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
any is the loosest constraint (no operations allowed beyond assignment
and comparison-to-nil for interface types); a constraint like
comparable permits ==/!=, which is what makes a generic map key
type possible.
21.3 A generic data structure
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) {
s.items = append(s.items, v)
}
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
last := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return last, true
}
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
v, _ := intStack.Pop() // v == 2
21.4 When not to reach for generics
If an interface with one or two methods already expresses what you need
(Chapter 9), prefer it — generics shine for data structures and
algorithms parameterized purely by type (a generic Stack[T], a
generic Map/Filter/Reduce over slices), not as a default replacement
for interfaces or as a way to avoid writing two similar 5-line functions.
21.5 Real-world example
A generic LRU cache — Cache[K comparable, V any] — is strictly better
than the map[string]*Node version from
projects/lru_cache.md: the same code now
works for Cache[int, User] or Cache[string, []byte] with zero
duplication and full compile-time type safety. Chapter 26 builds this
version.