Generics in Go
Generics add type parameters to functions and types for reusable, type-safe code.
Basics
Type parameters are declared in brackets. Use any for unconstrained parameters; constraints restrict allowed operations.
package main
func Identity[T any](x T) T { return x }
Constraints
// Built-in: any, comparable
// Custom constraint
type Number interface { ~int | ~int64 | ~float64 }
func Add[T Number](a, b T) T { return a + b }
Notes:
- The
~(tilde) allows underlying types to match (e.g., type MyInt int satisfies~int) comparablepermits==/!=; you cannot constrain to methods—use interfaces on methods for that
Generic helpers
func Map[T any, R any](in []T, f func(T) R) []R {
out := make([]R, len(in))
for i, v := range in { out[i] = f(v) }
return out
}
func Filter[T any](in []T, pred func(T) bool) []T {
out := make([]T, 0, len(in))
for _, v := range in { if pred(v) { out = append(out, v) } }
return out
}
Generic types
type Stack[T any] struct { data []T }
func (s *Stack[T]) Push(v T) { s.data = append(s.data, v) }
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.data) == 0 { return zero, false }
v := s.data[len(s.data)-1]
s.data = s.data[:len(s.data)-1]
return v, true
}
Design: keep generic types small and focused; avoid exposing concrete internals.
Comparable
func IndexOf[T comparable](xs []T, target T) int {
for i, v := range xs { if v == target { return i } }
return -1
}
Example usage
nums := []int{1,2,3}
doubles := Map(nums, func(n int) int { return n*2 })
ix := IndexOf(doubles, 4) // 1
Best practices
- Keep constraints minimal; prefer
anyunless you truly need operations - Avoid over-generalization; start concrete, then generalize
- Use descriptive type parameter names (T, K, V, In, Out) where helpful
- Prefer readability over clever type gymnastics
- Benchmark; sometimes specialized concrete versions outperform generic ones in hot paths
Summary
- Type parameters + constraints enable reusable, safe abstractions
- Generic funcs and types integrate naturally with Go’s simplicity