Generics remove copy-paste and any-casting from containers and algorithms while staying type-safe.
Since Go 1.18, functions and types can take type parameters in square brackets:
func Map[T, U any](xs []T, f func(T) U) []UEach type parameter has a constraint — an interface describing what the type must support:
any— no requirements; you can only move and store values.comparable— supports==and!=, required for map keys.cmp.Ordered— supports<,>; covers integers, floats and strings.- Your own:
type Number interface { ~int | ~int64 | ~float64 }. A type set like this lists permitted types;~intmeans "any type whose underlying type is int", so atype Celsius intqualifies too. Constraints can also require methods.
Callers rarely write type arguments: in Map(nums, strconv.Itoa) the compiler infers T and U. You need explicit arguments only when inference has nothing to go on, such as Zero[int]().
Generic types work the same way: type Stack[T any] struct{ items []T }, with methods func (s *Stack[T]) Push(v T). Methods cannot introduce new type parameters of their own — write a top-level function instead.
When to use them: containers, and algorithms over slices, maps and channels (the slices and maps packages are generic). When not to: if an ordinary interface with methods expresses the behaviour, prefer it. A good rule is to write the concrete code first and introduce type parameters when you find yourself duplicating it for several types.
Inside generic code you can't use a type switch on T directly; you'd convert to any first, which is a sign generics may be the wrong tool.
Example
package main
import (
"cmp"
"fmt"
"strconv"
)
func Map[T, U any](xs []T, f func(T) U) []U {
out := make([]U, 0, len(xs))
for _, x := range xs {
out = append(out, f(x))
}
return out
}
func MaxOf[T cmp.Ordered](first T, rest ...T) T {
m := first
for _, v := range rest {
m = max(m, v)
}
return m
}
func main() {
fmt.Println(Map([]int{1, 2, 3}, strconv.Itoa)) // [1 2 3] as []string
fmt.Println(MaxOf(3, 9, 4), MaxOf("b", "a")) // 9 b
}Remember
- Type parameters go in brackets with a constraint:
[T cmp.Ordered] ~Tin a constraint admits types with underlying type T- Type arguments are usually inferred from the call
Watch out for
- Reaching for generics where a plain interface with methods is simpler and clearer.