Go Cheatsheet
Generics
Use this Go reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Type Parameters
Generics landed in Go 1.18. A function or type takes type parameters in square brackets before the value parameters.
// T is the type parameter, any is its constraint func First[T any](s []T) (T, bool) { if len(s) == 0 { var zero T // the only way to write "zero value of T" return zero, false } return s[0], true } n, ok := First([]int{1, 2, 3}) // T inferred as int s, ok := First([]string{"a"}) // T inferred as string // Several parameters, and one constraining another func MapSlice[T, U any](in []T, f func(T) U) []U { out := make([]U, 0, len(in)) for _, v := range in { out = append(out, f(v)) } return out } lens := MapSlice([]string{"go", "rust"}, func(s string) int { return len(s) })
var zero T is the idiom for a generic zero value. You cannot write nil or 0,
since T might be neither.
Constraints
A constraint is an interface used in a type-parameter list. On top of methods it may list type unions.
type Number interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64 } func Sum[T Number](nums []T) T { var total T for _, n := range nums { total += n } return total } // Inline, no named constraint needed func Double[T ~int | ~float64](v T) T { return v * 2 }
The ~ operator means "any type whose underlying type is this", so named
types satisfy the constraint too:
type Celsius float64 Double(Celsius(21)) // works with ~float64, fails with a bare float64
| Constraint | Meaning |
|---|---|
any | anything, alias for interface{} |
comparable | usable with == and !=, so valid as a map key |
cmp.Ordered | ordered by <, <=, >, >= (1.21+, package cmp) |
~T | any type with underlying type T |
A | B | union of terms |
| method set | a normal interface, e.g. fmt.Stringer |
golang.org/x/exp/constraints predates cmp and still offers Integer,
Float, Signed, Unsigned and Complex. Its constraints.Ordered is
superseded by cmp.Ordered in the standard library, so reach for cmp first
and pull in x/exp only for the numeric families.
A union may not mix methods with type terms, and a type in a union cannot itself be an interface with methods.
Generic Types and Methods
type Stack[T any] []T func (s *Stack[T]) Push(v T) { *s = append(*s, v) } func (s *Stack[T]) Pop() (T, bool) { var zero T if len(*s) == 0 { return zero, false } old := *s v := old[len(old)-1] *s = old[:len(old)-1] return v, true } var s Stack[string] // instantiation is explicit for types s.Push("a") v, ok := s.Pop() // A generic struct with two parameters type Cache[K comparable, V any] struct { mu sync.RWMutex m map[K]V } func NewCache[K comparable, V any]() *Cache[K, V] { return &Cache[K, V]{m: make(map[K]V)} } func (c *Cache[K, V]) Get(k K) (V, bool) { c.mu.RLock() defer c.mu.RUnlock() v, ok := c.m[k] return v, ok } c := NewCache[string, int]()
Methods cannot declare their own type parameters. A method may only use the parameters of its receiver type. If you need a second type on a method, make it a package-level function.
// Illegal // func (s *Stack[T]) MapTo[U any](f func(T) U) Stack[U] { ... } // Legal func MapStack[T, U any](s Stack[T], f func(T) U) Stack[U] { /* ... */ }
Type Inference
Inference works from the value arguments, never from the return type or the assignment target.
First([]int{1}) // inferred MapSlice(xs, f) // inferred from both args // A type parameter that appears only in the result must be written out func Zero[T any]() T { var z T; return z } z := Zero[int]() // Zero() does not compile // Same when the arg is an untyped nil or an empty literal func Keys[K comparable, V any](m map[K]V) []K { /* ... */ } Keys(map[string]int{}) // fine Keys[string, int](nil) // nil alone gives inference nothing to work with // Partial instantiation is allowed, left to right func Convert[T, U any](in []T) []U { /* ... */ } Convert[int, string](xs) // both, U cannot be inferred
Generic types are always instantiated explicitly (Stack[int]), only
generic functions infer. Assigning a generic function to a variable also requires
instantiating it first: f := First[int].
Standard Library Generics (1.21+)
The builtins min, max and clear arrived in 1.21 and need no import.
min(3, 1, 2) // 1, works on any ordered type max(2.5, 7.1) // 7.1 clear(m) // empty a map, or zero every slice element
slices and maps are the generic workhorses:
import ("cmp"; "maps"; "slices") slices.Sort(xs) // ordered types slices.SortFunc(people, func(a, b Person) int { // -1, 0, +1 return cmp.Compare(a.Age, b.Age) }) slices.SortStableFunc(people, byAge) slices.Contains(xs, 3) // bool slices.Index(xs, 3) // int, -1 if absent slices.IndexFunc(xs, func(v int) bool { return v > 10 }) i, found := slices.BinarySearch(sorted, 7) // sorted input required xs = slices.Insert(xs, 1, 9, 8) // insert at index 1 xs = slices.Delete(xs, 1, 3) // drop [1,3) xs = slices.DeleteFunc(xs, func(v int) bool { return v < 0 }) slices.Equal(a, b) slices.Reverse(xs) xs = slices.Compact(xs) // dedupe adjacent lo, hi := slices.Min(xs), slices.Max(xs) ys := slices.Clone(xs) cmp.Compare(a, b) // -1 / 0 / +1 cmp.Or(a, b, c) // first non-zero value
slices.Insert, Delete, Compact and DeleteFunc return a slice and may
reuse the backing array, so always reassign and never keep using the old header.
Iterators (1.23+)
iter.Seq[V] is func(yield func(V) bool), and range accepts one directly.
maps.Keys and maps.Values return iterators, not slices.
for k := range maps.Keys(m) { /* ... */ } // iter.Seq[K] for k, v := range maps.All(m) { /* ... */ } // iter.Seq2[K, V] keys := slices.Sorted(maps.Keys(m)) // []K, sorted vals := slices.Collect(maps.Values(m)) // []V for i, v := range slices.Backward(xs) { /* ... */ } // reverse with index // Writing your own: yield returns false when the caller breaks func Filter[T any](s []T, keep func(T) bool) iter.Seq[T] { return func(yield func(T) bool) { for _, v := range s { if keep(v) && !yield(v) { return } } } } for v := range Filter(xs, func(v int) bool { return v%2 == 0 }) { fmt.Println(v) }
x/exp/maps.Keys returned a []K, the stdlib maps.Keys returns a sequence.
Mixing them up is the most common upgrade error. Use
slices.Collect or slices.Sorted to get a slice back.
When Not to Use Generics
Generics are for algorithms over containers, not for abstraction in general.
- If the function only calls methods on its argument, take an interface. That
is what interfaces are for, and it compiles smaller and reads better.
- Generics do not help you write polymorphic methods, since a method cannot
add type parameters.
- One implementation, one call site, one type: write the concrete function.
- A constraint with a long type union usually means the operation belongs on a
method instead.
- any in a signature that then type-switches is not generics, it is a type
switch with extra steps.
The Go team's own guidance: write the concrete code first, then factor out a type parameter when you have written the same body two or three times for different types.