How does type inference work? When does it fail, and how do you order type parameters to make partial instantiation pleasant?
Question 119HardGo 1.22 to 1.25
Go infers type arguments in two ways:
- Function argument inference unifies parameter types with argument types.
- Constraint type inference derives a parameter from another parameter's constraint. For example, it gets
EfromS ~[]E.
Inference never uses the result type or the assignment target. The exception, since Go 1.21, is assigning a generic function to a variable or parameter of explicit function type.
func Zero[T any]() T { var z T; return z }
// v := Zero() // error: cannot infer T
v := Zero[int]()
// Put non-inferable params FIRST so callers can partially instantiate
func Convert[To, From ~int | ~float64](f From) To { return To(f) }
x := Convert[float64](3) // To=float64 explicit, From=int inferred
// Go 1.21: mixed untyped constants choose the "larger" kind
fmt.Println(max2(1, 2.5)) // T inferred as float64 -> 2.5
func max2[T cmp.Ordered](a, b T) T { if a > b { return a }; return b }
// Go 1.21: generic func passed to generic func
xs := []int{3, 1, 2}
slices.SortFunc(xs, cmp.Compare) // cmp.Compare[int] inferred
Gotchas:
- With untyped
nilnothing can be inferred. - Inference does not look through interfaces. If a method-only constraint is the only clue, you must pass type arguments explicitly.
- Before 1.21,
max2(1, 2.5)failed to compile.
More on Generics
- Q117What is
comparable? What changed in Go 1.20, and what does this program print? - Q118Which operations can you perform on a value of type parameter type? Why can't you access a struct field even when every type in the set has it?
- Q120Can you store a generic function in a variable or pass it around without instantiating it?
- Q121How do you get and check the zero value of a type parameter
T? - Q122What does this print? (nil checks inside generic code)
- Q123What does this print? (type switch on a type parameter)