How it works
The explanation, with examples you can run.
Functions and constraints
package main import ( "cmp" "fmt") type Number interface { ~int | ~int64 | ~float64} func Sum[T Number](xs []T) T { var total T for _, x := range xs { total += x } return total} func MaxOf[T cmp.Ordered](xs ...T) T { m := xs[0] for _, x := range xs[1:] { m = max(m, x) } return m} type Celsius float64 // underlying type float64, so it satisfies ~float64 func main() { fmt.Println(Sum([]int{1, 2, 3})) fmt.Println(Sum([]Celsius{20.5, 21})) fmt.Println(MaxOf("pear", "apple", "zucchini"))}It prints 6, 41.5 and zucchini. Notice:
- Inference.
Sum([]int{...})doesn't saySum[int]; the compiler works it out from the arguments. ~Tmeans "any type whose underlying type is T". Without the tilde,Celsiuswould be rejected.- The operations allowed on a
Tare those that every type in the constraint's set supports. That is whytotal += xcompiles: all three types support+. - Built-in constraints.
anyallows any type.comparableallows==, which map keys need.cmp.Ordered(Go 1.21) allows<. The built-inminandmaxalso arrived in 1.21.
Generic types
type Set[T comparable] map[T]struct{} func (s Set[T]) Add(v T) { s[v] = struct{}{} }func (s Set[T]) Has(v T) bool { _, ok := s[v]; return ok }A generic type must be instantiated before use (Set[string]{}). The standard library now has many generic helpers: slices.Sort, slices.Index, maps.Keys and the iterator types in iter (Go 1.23).
Generic methods (Go 1.27)
Until Go 1.26 a method could only use its receiver's type parameters, so helpers that needed a new one had to be package-level functions. Go 1.27 lets a method declare its own:
func (b *Bag) Count[T any]() int { n := 0 for _, it := range b.items { if _, ok := it.(T); ok { n++ } } return n}Called as b.Count[int](). Interface methods still can't have type parameters, and a generic method can't be used to satisfy an interface method. math/rand/v2 now has a (*Rand).N method alongside the generic rand.N function.
How it compiles
The language leaves the strategy to the compiler. The gc compiler doesn't usually make one copy per type argument: it compiles one copy per shape, where the shape depends on properties such as the type's size and where it holds pointers, so many type arguments share one compiled body. That shared body works much like a function with an interface parameter, so generic code is not automatically as fast as code written for one type. Measure before rewriting a hot path.
When to use them
Use generics for containers, and for algorithms over slices, maps and channels, where the caller should get its own element type back. When the code calls a few methods on a value, an ordinary interface parameter is usually simpler to read and just as fast.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.