Generics in Go: Type Parameters by Example
Generics arrived in Go 1.18. They let you write functions and types that work for many types while staying type-safe, without falling back to interface{} and type assertions.
A generic function
func Map[T, U any](items []T, fn func(T) U) []U {
out := make([]U, 0, len(items))
for _, it := range items {
out = append(out, fn(it))
}
return out
}
names := Map(users, func(u User) string { return u.Name }) // []string
Constraints
import "cmp"
func Max[T cmp.Ordered](a, b T) T { // ints, floats, strings
if a > b {
return a
}
return b
}
func Contains[T comparable](items []T, target T) bool { // types usable with ==
for _, it := range items {
if it == target {
return true
}
}
return false
}
Custom constraints
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
type Rupees int64 // ~int64 means types based on int64 are allowed too
Sum([]Rupees{100, 250})
Generic types
type Cache[K comparable, V any] struct {
mu sync.Mutex
items map[K]V
}
func NewCache[K comparable, V any]() *Cache[K, V] {
return &Cache[K, V]{items: make(map[K]V)}
}
func (c *Cache[K, V]) Get(key K) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
v, ok := c.items[key]
return v, ok
}
users := NewCache[int, User]()
Use the standard library first
The slices and maps packages already provide generic helpers: slices.Contains, slices.Sort, slices.Index, maps.Keys and more.
When not to use generics
If an interface with methods expresses the behaviour you need (like io.Reader), use the interface. Generics are best for containers and algorithms that work the same way for every type.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.