Generics
Before Go 1.18, a function to find the larger of two values had to be written
once per type, or take any and give up compile-time safety. Type
parameters solve it properly:
package main
import "fmt"
// T is a type parameter; cmp.Ordered-style constraints come later —
// here we accept any type that supports > via a custom constraint.
type Number interface {
~int | ~int64 | ~float64
}
func Max[T Number](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 7)) // T inferred as int
fmt.Println(Max(2.5, 1.5)) // T inferred as float64
}
The square brackets declare T, a placeholder type the caller fills in. You
rarely fill it in by hand — Max(3, 7) lets the compiler infer T from the
arguments. Unlike any-based tricks, Max("a", 1) is a compile error, and no
type assertions appear anywhere.
Constraints
[T Number] reads "any type T that satisfies Number". A constraint is an
interface, used at compile time to say what the function may do with T:
any— no requirements. Fine when you only pass values around (store them, return them, hand them to a callback).comparable— types usable with==and!=; required to useTas a map key or to search for equal elements.- Custom constraints — interfaces listing methods, or unions of types
like
Numberabove (~intmeans "int or any type whose underlying type is int" — yourCelsiusfrom earlier would satisfy~float64).
The compiler enforces both directions: the function body may only use operations the constraint guarantees, and callers may only instantiate with types that satisfy it.
Generics or interfaces?
They answer different questions, and interfaces should remain your first instinct:
- Interfaces abstract over behavior: "I need anything that can
Write." The concrete type varies at run time — one[]io.Writercan mix files and buffers. - Generics abstract over types while keeping them: "a slice of T in, a slice of U out, and the compiler still knows what T and U are." Right for data structures and slice/map utilities, where an interface would erase the element type and force assertions on the way out.
If your generic function's constraint is just a list of methods and you never
return T, you probably wanted a plain interface parameter. Go's own advice:
write concrete code first, and reach for type parameters when you notice
yourself writing the same function per type.
A practical shape: Map
The exercise is the classic slice transformer:
func Map[T, U any](xs []T, f func(T) U) []U
Two type parameters: element type in, element type out. Map never inspects
its elements — it only shuttles them through f — so any is the honest
constraint. Build a result slice (make([]U, 0, len(xs)) pre-allocates the
right capacity), apply f to each element, and append. Ints to strings,
strings to their lengths — one function, fully type-checked.
Practice
1. A Generic Map
IntermediateImplement the classic slice transformer as a generic function:
func Map[T, U any](xs []T, f func(T) U) []UMapreturns a new slice containingf(x)for every elementxofxs, in order. It must work for any input and output types — the tests use it in both directions:// int → string Map([]int{1, 2, 3}, func(n int) string { return strconv.Itoa(n) }) // []string{"1", "2", "3"} // string → int Map([]string{"go", "gopher"}, func(s string) int { return len(s) }) // []int{2, 6}Requirements:
- The result has the same length as
xs, elements in the same order. - An empty (or nil) input produces an empty result.
- Don't modify the input slice.
Both type parameters are constrained by
anybecauseMapnever looks inside the elements — it only passes them tof. Note there's not a single type assertion involved: the compiler knowsTandUat every call site.package main import ( "fmt" "strconv" ) // Map returns a new slice holding f(x) for every element x of xs, in order. func Map[T, U any](xs []T, f func(T) U) []U { // TODO: implement return nil } func main() { words := Map([]int{1, 2, 3}, func(n int) string { return strconv.Itoa(n) }) fmt.Println(words) lengths := Map([]string{"go", "gopher"}, func(s string) int { return len(s) }) fmt.Println(lengths) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- The result has the same length as