Lesson 8 · 8 of 20
Generics: type parameters and constraints · Go
Write generic functions in Go with type parameters and constraints: one Sum for several number types and one Map for any type.

Since Go 1.18 a function can have type parameters, in square brackets before the ordinary ones: Sum[T Number](values []T) T. T is a type chosen at the call, and the same function adds up integers, decimals and cents without writing it three times or falling back on any.
Number is a constraint: an interface that, instead of methods, lists the allowed types with |. Thanks to it the compiler knows they all support +=, so total += v is allowed. The tilde in `~int64` means int64 or any type whose underlying type is int64, which is why Cents gets in too.
var total T makes the zero value of T: 0 for numbers. At the call, Go infers T from the arguments: with []int{3, 5, 8}, T is int. With nil there is nothing to infer it from, so you spell it out: Sum[float64](nil) returns 0.
Map[T, U any] has two type parameters and the `any` constraint, which accepts every type but lets you do almost nothing with it: store it, pass it and return it. That is enough to apply f to each item: from []int to []string ([#7 #12 #30], and %T confirms []string) and from there to []int with the lengths.
To compare values with == there is the `comparable` constraint. In Go 1.19 the standard library has none for < and >; you can declare your own just like Number, with the types you need.
Code · main.go
package mainimport "fmt"// Number is a constraint: the set of types that T may be.type Number interface{ ~int | ~int64 | ~float64 }type Cents int64 // ~int64 also lets in named types such as Centsfunc Sum[T Number](values []T) T { var total T // the zero value of T for _, v := range values { total += v } return total}// Map works for any input type T and any output type U.func Map[T, U any](items []T, f func(T) U) []U { out := make([]U, 0, len(items)) for _, item := range items { out = append(out, f(item)) } return out}func main() { fmt.Println(Sum([]int{3, 5, 8}), Sum([]float64{2.5, 1.25}), Sum([]Cents{1999, 450})) fmt.Println(Sum[float64](nil)) // explicit type argument labels := Map([]int{7, 12, 30}, func(n int) string { return fmt.Sprintf("#%d", n) }) fmt.Printf("%v %T\n", labels, labels) fmt.Println(Map(labels, func(s string) int { return len(s) }))}Output · go run main.go
16 3.75 2449 0 [#7 #12 #30] []string [2 3 3]
Key points
- Type parameters go in square brackets:
func Sum[T Number]. - A constraint is an interface that says which types are allowed.
~int64also lets in types defined on top ofint64.- Go infers
Tfrom the arguments; if it cannot, writeSum[float64]. anyaccepts every type;comparable, those you can compare with==.
Exercise
Write Filter[T any](items []T, keep func(T) bool) []T and use it to keep the amounts in []Cents{1999, 450, 12000} greater than 1000. Then add up the result with Sum.