Generics
Introduction
Section titled “Introduction”Go 1.18 (released March 2022) added generics via type parameters. Generics allow you to write Functions and types that abstract over different concrete types while maintaining full type safety At compile time.
Type Parameters
Section titled “Type Parameters”A generic function declares type parameters in square brackets after the function name:
func Map[T, U any](s []T, f func(T) U) []U { result := make([]U, len(s)) for i, v := range s { result[i] = f(v) } return result}Call with explicit type arguments:
nums := []int{1, 2, 3}strs := Map[int, string](nums, func(n int) string { return strconv.Itoa(n)})fmt.Println(strs) // [1 2 3]In most cases, the compiler infers type arguments:
strs := Map(nums, func(n int) string { return strconv.Itoa(n)})Constraints
Section titled “Constraints”A constraint is an interface that restricts which types can be used as a type argument. The any Constraint (alias for interface{}) allows any type:
func Print[T any](v T) { fmt.Printf("%v\n", v)}Built-in Constraints
Section titled “Built-in Constraints”The cmp package provides ordered type constraints:
import "cmp"
func Max[T cmp.Ordered](a, b T) T { if a > b { return a } return b}
fmt.Println(Max(3, 7)) // 7fmt.Println(Max(3.14, 2.71)) // 3.14fmt.Println(Max("a", "z")) // zcmp.Ordered is defined as:
type Ordered interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string}Custom Constraints
Section titled “Custom Constraints”Define constraints as interfaces with type elements:
type Number interface { ~int | ~int64 | ~float64}
func Sum[T Number](nums []T) T { var total T for _, n := range nums { total += n } return total}The ~ token means “the underlying type must be.” A type MyInt int has underlying type intSo It satisfies ~int.
Methods in Constraints
Section titled “Methods in Constraints”Constraints can include methods alongside type elements:
type Stringer interface { ~string | ~[]byte String() string}
func Describe[T Stringer](v T) { fmt.Println(v.String())}This constraint requires the type to have an underlying type of string or []byte AND to have a String() string method.
The constraints Package
Section titled “The constraints Package”The golang.org/x/exp/constraints package provides additional useful constraints:
import "golang.org/x/exp/constraints"
func Clamp[T constraints.Ordered](v, lo, hi T) T { if v < lo { return lo } if v > hi { return hi } return v}Common constraints:
| Constraint | Types Allowed |
|---|---|
constraints.Signed | ~int``~int8``~int16``~int32``~int64 |
constraints.Unsigned | ~uint``~uint8``~uint16``~uint32``~uint64``~uintptr |
constraints.Integer | All signed and unsigned integers |
constraints.Float | ~float32``~float64 |
constraints.Ordered | All integers, floats, and ~string |
constraints.Complex | ~complex64``~complex128 |
Generic Types
Section titled “Generic Types”Types can also have type parameters:
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) { if len(s.items) == 0 { var zero T return zero, false } v := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return v, true}
func (s *Stack[T]) Len() int { return len(s.items)}Usage:
s := Stack[int]{}s.Push(1)s.Push(2)v, ok := s.Pop()fmt.Println(v, ok) // 2 trueGeneric Slices
Section titled “Generic Slices”type Set[T comparable] struct { items map[T]struct{}}
func NewSet[T comparable]() *Set[T] { return &Set[T]{items: make(map[T]struct{})}}
func (s *Set[T]) Add(v T) { s.items[v] = struct{}{}}
func (s *Set[T]) Contains(v T) bool { _, ok := s.items[v] return ok}Generic Maps
Section titled “Generic Maps”type Pair[K comparable, V any] struct { Key K Value V}
func MapKeys[K comparable, V any](m map[K]V) []K { keys := make([]K, 0, len(m)) for k := range m { keys = append(keys, k) } return keys}Type Inference
Section titled “Type Inference”Go uses type inference for generic functions. The compiler infers type arguments from function Arguments. When inference is ambiguous, you must specify explicitly:
func New[T any]() *T { return new(T)}
// Ambiguous -- no arguments to infer from:var p *int = New[int]() // must specify
// Unambiguous -- inferred from argument:s := Map(nums, fn) // T and U inferred from nums and fnGeneric Methods
Section titled “Generic Methods”Methods on generic types can have their own type parameters (separate from the receiver”s type Parameters):
func (s *Stack[T]) Filter(predicate func(T) bool) *Stack[T] { result := &Stack[T]{} for _, v := range s.items { if predicate(v) { result.Push(v) } } return result}Note: methods cannot introduce new type parameters that are not on the receiver. All type parameters Must be declared on the type.
Instantiation
Section titled “Instantiation”Go uses monomorphization at compile time. Each unique set of type arguments produces a separate Specialization of the generic function or type. There is no boxing or type erasure at runtime.
Limitations
Section titled “Limitations”No specialization. You cannot provide different implementations for different types. Generic code is the same for all type arguments.
No operator methods in constraints. You cannot require that a type support
+beyond the built-in types. You cannot write a constraint that says “any type with a+operator.”No variadic type parameters. Type parameter lists must be fixed-length.
No type parameter defaults. Each type parameter must be specified or inferred.
Methods cannot add type parameters. Only the type’s own type parameters are available in methods.
Intuition
Section titled “Intuition”Generics are molds, not copies: Before generics, writing a Max function for int and another for float64 meant maintaining two near-identical copies. Generics let you write one mold — the compiler stamps out type-specific versions at compile time, like a factory using the same blueprint to produce widgets in steel or plastic. No runtime cost, no boxing, just a single source of truth.
Why it matters: Generics eliminate the tension between code reuse and type safety. You no longer need to choose between any (unsafe) or copy-pasting (fragile) when writing collection utilities, algorithms, or data structures.
The key insight: Constraints are the “material specification” for your mold — they tell the compiler which operations are safe to perform on the generic type.
Common Pitfalls
Section titled “Common Pitfalls”Using
anyas a constraint when a narrower constraint exists.anyallows any type, including types that do not support the operations your function performs. Usecomparablecmp.OrderedOr a custom constraint to enforce requirements at compile time.Forgetting
comparablefor map keys and set elements. If a generic type uses a value as a map key, the constraint must includecomparable.Confusing
~TwithT.~intmatchesintand any type with underlying typeint(e.g.,type MyInt int).intmatches onlyintexactly.Overusing generics. Not every function needs to be generic. If a function only works with one or two concrete types, concrete implementations are clearer and often more efficient.
Generic type assertion ambiguity. When type parameters appear in the same signature position as concrete types, inference may fail. Specify type arguments explicitly in such cases.
Summary
Section titled “Summary”This topic covers the core concepts of generics, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Cross-References
Section titled “Cross-References”- Interfaces: Type constraints in generics build on interface satisfaction.
- Error Handling: Generic error handling patterns with type parameters.
- Arrays, Slices, and Maps: Generic collection operations replacing type-specific implementations.