Type parameters on functions
Before Go 1.18 you had two choices for code that should work on several types: copy it once per type (maxInt, maxFloat, ...) or accept any and give up type safety. Generics add a third: a function declares type parameters in square brackets before its normal parameters, func Max[T int | float64 | string](a, b T) T, and the compiler checks every call.
The part after the name, int | float64 | string, is the constraint: the set of types T may be, and therefore what you may do with a T (here, compare with >). Usually you do not write Max[int](3, 7) — the compiler infers T from the arguments. Write it explicitly when inference cannot work, or to force a type: Max[float64](3, 7.5) turns the untyped 3 into a float64.
package main
import "fmt"
// Without generics: one function per type.
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
// With generics: T is a type parameter, constrained to ordered types.
func Max[T int | float64 | string](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(maxInt(3, 7))
fmt.Println(Max(3, 7)) // T inferred as int
fmt.Println(Max(2.5, 1.5)) // T inferred as float64
fmt.Println(Max("go", "rust")) // T inferred as string
fmt.Println(Max[float64](3, 7.5)) // explicit: 3 becomes float64
}7
7
2.5
rust
7.5Call Max(3, 7.5) without the explicit type argument. It compiles — work out which T the compiler picked from the two untyped constants, then check by printing with %T.
Max[int] is an ordinary, non-generic function func(int, int) int. Each call site is type-checked against its instantiation, so a mistake is a compile error, never a runtime panic.invalid operation: a > b (type parameter T cannot use operator >)
package main
import "fmt"
func Max[T any](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 7))
}# command-line-arguments
./main.go:6:5: invalid operation: a > b (type parameter T cannot use operator >)Inside a generic function you may only do what the constraint allows for every type in it. any includes structs, slices and functions, none of which support >, so the body is rejected — even though the only call uses ints.
Tighten the constraint to one that guarantees ordering: cmp.Ordered from the standard library.
package main
import (
"cmp"
"fmt"
)
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 7))
}