Go Generics
The Point
Generics (Go 1.18+) let you write type-parameterized functions – the type is determined at call time, but type checking happens at compile time. This gives you both flexibility and safety.
Explanation
Without Generics vs with Generics
1// Without generics: same logic duplicated for each type
2func sumInts(nums []int) int {
3 var total int
4 for _, n := range nums { total += n }
5 return total
6}
7func sumFloats(nums []float64) float64 {
8 var total float64
9 for _, n := range nums { total += n }
10 return total
11}
12
13// Or use any, but lose type safety -- errors only show up at runtime
14func sumAny(nums []any) any { ... }
15
16// With generics: one function, compile-time type safety
17func sum[T int | float64](nums []T) T {
18 var total T
19 for _, n := range nums { total += n }
20 return total
21}
22
23sum([]int{1, 2, 3}) // T = int
24sum([]float64{1.1, 2.2}) // T = float64Syntax breakdown
1func ptr[T any](v T) *T { return &v }
2// ^^^^^^ ^ ^
3// | | return type is also T
4// | parameter type is T
5// type parameter: T is the name, any is the constraintT– type parameter name, convention is a single uppercase letterany– constraint, meaningTcan be any type
Constraints can be narrowed
1// Only accept numbers
2func sum[T int | float64](nums []T) T { ... }
3
4// Only accept types that implement String()
5func print[T fmt.Stringer](v T) { ... }Type inference: usually no need to specify explicitly
1ptr("hello") // inferred: T = string
2ptr[string]("hello") // explicit, same resultKnowledge Sugar
Generics vs any vs interface
any | interface (with methods) | Generics | |
|---|---|---|---|
| Type resolved at | Runtime | Runtime | Compile time |
| Type safety | No | Partial | Yes |
| Best for | Truly type-agnostic code | Constraining behavior | Same logic across multiple types |
When to use Generics? When the same logic needs to work across multiple types and you want compile-time type error detection. Pointer helpers and collection operations (map, filter, reduce) are the most common examples.