Generics: type parameters

Go · Advanced · 15 min

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) []U

Each 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; ~int means "any type whose underlying type is int", so a type Celsius int qualifies 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]
  • ~T in 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.

Check yourself

  1. `type Celsius float64`. Which constraint lets `Sum[Celsius]` compile?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: go.dev/doc/tutorial/generics