Idiomatic Generics
Overview
Generics are powerful but should be used judiciously. This chapter covers when to use generics vs interfaces, and best practices.
When to Use Generics
✅ Good uses: - Container types (Stack, Set, Queue) - Utility functions (Map, Filter, Reduce) - Type-safe concurrent constructs - Avoiding reflection
❌ Don’t use when: - Interface works fine - Only one type will ever be used - Adding complexity without benefit
Generics vs Interfaces
Use Interface When:
// Behavior-based polymorphism
type Writer interface {
Write([]byte) (int, error)
}
func Save(w Writer, data []byte) error {
_, err := w.Write(data)
return err
}Use Generics When:
// Type preservation needed
func Reverse[T any](s []T) []T {
result := make([]T, len(s))
for i, v := range s {
result[len(s)-1-i] = v
}
return result
}Guidelines
1. Start with Concrete Types
// Start here
func ReverseInts(s []int) []int { }
// Generalize only when needed
func Reverse[T any](s []T) []T { }2. Use Meaningful Constraints
// Too broad
func Process[T any](v T) { }
// Better: documents requirements
func Process[T constraints.Ordered](v T) { }3. Don’t Over-Constrain
// Over-constrained
type Number interface {
int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 |
float32 | float64
}
// Better: use existing constraint
func Sum[T constraints.Integer | constraints.Float](nums []T) T4. Type Inference is Your Friend
// Let Go infer when obvious
result := Map(nums, double)
// Explicit only when needed
result := Convert[int, float64](nums)Common Patterns
slices Package (stdlib)
import "slices"
slices.Sort(nums)
slices.Reverse(nums)
slices.Contains(nums, 5)
slices.Index(nums, 5)maps Package (stdlib)
import "maps"
maps.Clone(m)
maps.Keys(m)
maps.Values(m)Summary
| Decision | Choice |
|---|---|
| Need type preservation | Generics |
| Need polymorphic behavior | Interface |
| Operating on containers | Generics |
| Method set requirements | Interface |