Frontend Labs
⋯

31. Advanced generics: constraints, generic algorithms, inference limits

31.1 Custom constraint interfaces

type Numeric interface {
	~int | ~int32 | ~int64 | ~float32 | ~float64
}

func Average[T Numeric](nums []T) float64 {
	var sum T
	for _, n := range nums {
		sum += n
	}
	return float64(sum) / float64(len(nums))
}

The ~ ("approximation element") means "this type, or any type whose underlying type is this" — so a type Celsius float64 still satisfies ~float64, letting your generic function work with named types, not just the exact built-ins.

31.2 Generic functions over slices: Map, Filter, Reduce

func Map[T, U any](s []T, f func(T) U) []U {
	out := make([]U, len(s))
	for i, v := range s {
		out[i] = f(v)
	}
	return out
}

func Filter[T any](s []T, pred func(T) bool) []T {
	var out []T
	for _, v := range s {
		if pred(v) {
			out = append(out, v)
		}
	}
	return out
}

names := Map(users, func(u User) string { return u.Name })
active := Filter(users, func(u User) bool { return u.Active })

The standard library's slices and maps packages (Go 1.21+) already provide slices.Sort, slices.Contains, maps.Keys, and similar — reach for those before writing your own.

31.3 Where type inference runs out

Generic methods (as opposed to generic functions/types) don't exist — you cannot add a new type parameter to a method, only use the type parameters already declared on the receiver type. And inference across multiple unrelated type parameters sometimes needs an explicit hint:

// Won't infer U from context alone in many expressions — spell it out:
result := Map[User, string](users, func(u User) string { return u.Name })

31.4 Real-world example

Rewriting the Storage interface from Chapter 9 as Storage[K comparable, V any] gives you compile-time-checked, zero-reflection generic storage backends — the exact upgrade path from the 90-day plan's map[string]string-based cache to the projects/lru_cache.md design in Chapter 26.


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36