Generics: the compiler errors¶
Level: 301 · anyone reading a type-parameter error for the first time
One line: Eight errors the Go 1.25 compiler gives for type parameters, each recorded from a real build — what you were doing, the exact wording, the mistake, and the fix.
Every transcript below comes from a driver that writes the broken program and runs go build; the messages are the compiler's own, and the answer key records them so that a later Go rewording them shows up as a diff. Positions such as ./infer.go:21:22 are stable because the program is fixed.
cannot infer T¶
You called a generic function whose type parameter appears only in its result, or passed an argument that says nothing about the type — and then a []any fixed T before a []int argument arrived.
Verified output of generics_err_infer_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build infer.go
# command-line-arguments
./infer.go:21:22: in call to Zero, cannot infer T (declared at ./infer.go:5:11)
./infer.go:23:24: in call to Map, cannot infer U (declared at ./infer.go:10:13)
./infer.go:24:37: in call to Index, type []int of []int{…} does not match inferred type any for T
exit status 1
The mistake. Inference reads the arguments and stops: an int on the left of the assignment is not consulted, nil has no type, and once []any has made T be any, the literal []int{1} is a []int, which is not any — Inference works from arguments, not results.
The fix. Name the type argument, or give the argument a type:
var count int = Zero[int]()
fmt.Println(count, Map(names, strings.ToUpper))
fmt.Println(Index([]any{[]int{1}}, any([]int{1})))
cannot use generic function … without instantiation¶
You assigned a generic function to a variable, declared a variable of a generic type without type arguments, or gave a type too few of them.
Verified output of generics_err_instantiation_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build uninstantiated.go
# command-line-arguments
./uninstantiated.go:13:13: cannot use generic function Map without instantiation
./uninstantiated.go:14:15: cannot use generic type Stack[T any] without instantiation
./uninstantiated.go:15:12: not enough type arguments for type Pair: have 1, want 2
exit status 1
The mistake. A generic function or type is a template, not a value or a type, until every type parameter has an argument — written or inferred (A generic type and its zero value).
The fix.
method must have no type parameters¶
You gave a method its own type parameter, to convert a receiver's contents to a type the receiver does not know.
Verified output of generics_err_method_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build method.go
# command-line-arguments
./method.go:5:24: syntax error: method must have no type parameters
exit status 1
The mistake. Only the receiver's type parameters are in scope in a method; a method may not declare more (A method cannot have type parameters). It is a syntax error, so nothing else in the file is checked until it is gone.
The fix. A top-level function that takes the receiver:
func ConvertLedger[U any](l Ledger, f func(int) U) []U {
out := make([]U, 0, len(l.amounts))
for _, a := range l.amounts {
out = append(out, f(a))
}
return out
}
does not satisfy¶
You called a generic function with a type argument outside its constraint's set — a named type against a union without ~, a slice against comparable, a type that lacks the constraint's method.
Verified output of generics_err_satisfy_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build satisfy.go
# command-line-arguments
./satisfy.go:18:20: Celsius does not satisfy Exact (possibly missing ~ for float64 in Exact)
./satisfy.go:19:19: []int does not satisfy comparable
./satisfy.go:20:18: int does not satisfy Stringer (missing method String)
exit status 1
The mistake. Three ways out of the set. int | float64 names exactly two types, and Celsius is a third — the compiler's parenthesis says what is missing (A type parameter needs a constraint). []int has no ==. int has no String method.
The fix.
type Exact interface{ ~int | ~float64 } // now admits Celsius
// A slice has no ==: search with a function instead of Index.
i := slices.IndexFunc(rows, func(r []int) bool { return slices.Equal(r, want) })
fmt.Println(Show(Invoice{ID: 42})) // a type that has String()
cannot use type … outside a type constraint¶
You used an interface with a union as a parameter type, a variable's type, a slice's element type or an embedded field.
Verified output of generics_err_outside_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build outside.go
# command-line-arguments
./outside.go:6:2: cannot use type Number outside a type constraint: interface contains type constraints
./outside.go:9:17: cannot use type Number outside a type constraint: interface contains type constraints
./outside.go:12:12: cannot use type Number outside a type constraint: interface contains type constraints
./outside.go:13:16: cannot use type Number outside a type constraint: interface contains type constraints
exit status 1
The mistake. An interface with a union is a type set, and a type set is not a type: it constrains a [T …] and nothing else (Constraints are type sets).
The fix. A generic function where a value was wanted, or a basic interface where a variable was wanted:
func Describe[T Number](n T) string { return fmt.Sprint(n) }
type Reading struct{ Value float64 } // a concrete field, not an embedded set
type parameter T cannot use operator¶
You wrote >, +, == or unary - on a value whose constraint does not give every type in its set that operator.
Verified output of generics_err_operator_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build operators.go
# command-line-arguments
./operators.go:4:5: invalid operation: a > b (type parameter T cannot use operator >)
./operators.go:10:36: invalid operation: operator + not defined on a (variable of type T constrained by any)
./operators.go:12:40: invalid operation: a == b (incomparable types in type set)
./operators.go:16:48: invalid operation: operator - not defined on a (variable of type T constrained by ~int | ~string)
exit status 1
The mistake. An operator on a T is allowed only if every type in T's set has it (spec ↗). Under any none is. Under ~int | ~string, + is allowed because both halves have it, and - is not because strings do not. Note the three wordings for the three operators under any — only the + message says constrained by any.
The fix. The constraint that grants the operator:
func Larger[T cmp.Ordered](a, b T) T
func Add[T ~int | ~float64](a, b T) T
func Same[T comparable](a, b T) bool
func Negate[T ~int | ~float64](a T) T
cannot use type switch on type parameter value¶
You switched on v.(type), or asserted v.(string), where v has a type parameter type.
Verified output of generics_err_typeswitch_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build typeswitch.go
# command-line-arguments
./typeswitch.go:4:9: cannot use type switch on type parameter value v (variable of type T constrained by any)
./typeswitch.go:8:14: invalid operation: cannot use type assertion on type parameter value v (variable of type T constrained by any)
exit status 1
$ go run typeswitch_fixed.go
int string other
exit status 0
The mistake. A value of type parameter type is not an interface value, even under any as a constraint: any there names a set of types, and v is one of them, not a box holding one. Type switches and assertions (spec ↗) need an interface operand.
The fix. Convert to any first, as the second half of the transcript does — switch any(v).(type). When the switch is most of the function, the function did not need a type parameter: an ordinary any parameter, or an interface with the method the cases were reaching for, does the job (When To Use Generics ↗).
mismatched types int and int¶
You wrote a method for one instantiation, func (s *Stack[int]), meaning "this method exists only on Stack[int]".
Verified output of generics_err_receiver_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build receiver.go
# command-line-arguments
./receiver.go:8:3: invalid operation: total += v (mismatched types int and int /* with int declared at ./receiver.go:5:16 */)
./receiver.go:10:9: cannot use total (variable of type int) as int /* with int declared at ./receiver.go:5:16 */ value in return statement
exit status 1
The mistake. The identifiers in a receiver's brackets declare the method's type parameters; they do not pick an instantiation. Stack[int] declares a type parameter named int, which shadows the predeclared int, so total := 0 and v are two different ints — as the comment /* with int declared at ./receiver.go:5:16 */ is trying to say (FAQ ↗). Go has no specialization (A method cannot have type parameters).
The fix. A method that works for every T its constraint allows, or a function with a narrower constraint:
func (s *Stack[T]) Len() int { return len(s.items) }
func SumStack[T ~int | ~float64](s *Stack[T]) T {
var total T
for _, v := range s.items {
total += v
}
return total
}
The eight drivers
generics_err_infer_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# "cannot infer T": inference reads a call's arguments and nothing else. A type
# parameter that appears only in the result, or one whose argument is nil, has
# nothing to be inferred from -- and once an argument has fixed T, a later
# argument of another type does not widen it.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/infer.go" <<'GO'
package main
import "fmt"
func Zero[T any]() T {
var zero T
return zero
}
func Map[T, U any](in []T, f func(T) U) []U {
out := make([]U, 0, len(in))
for _, v := range in {
out = append(out, f(v))
}
return out
}
func Index[T comparable](values []T, want T) int { return -1 }
func main() {
var count int = Zero()
names := []string{"pear"}
fmt.Println(count, Map(names, nil))
fmt.Println(Index([]any{[]int{1}}, []int{1}))
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build infer.go'
(cd "$dir" && go build infer.go) 2>&1
echo "exit status $?"
generics_err_instantiation_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# A generic function or type is not a value or a type until it is instantiated.
# Assigning Map to a variable, declaring a Stack with no type argument, and
# giving Pair one argument instead of two are each refused.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/uninstantiated.go" <<'GO'
package main
type Stack[T any] struct{ items []T }
type Pair[K comparable, V any] struct {
Key K
Val V
}
func Map[T, U any](in []T, f func(T) U) []U { return nil }
func main() {
toLabel := Map
var invoices Stack
var entry Pair[string]
_, _, _ = toLabel, invoices, entry
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build uninstantiated.go'
(cd "$dir" && go build uninstantiated.go) 2>&1
echo "exit status $?"
generics_err_method_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# A method may not declare type parameters of its own. This is a syntax error:
# the parser refuses it before the type checker sees the file.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/method.go" <<'GO'
package main
type Ledger struct{ amounts []int }
func (l Ledger) Convert[U any](f func(int) U) []U {
out := make([]U, 0, len(l.amounts))
for _, a := range l.amounts {
out = append(out, f(a))
}
return out
}
func main() {}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build method.go'
(cd "$dir" && go build method.go) 2>&1
echo "exit status $?"
generics_err_satisfy_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# "does not satisfy": the type argument is outside the constraint's type set.
# Three ways in: a union term without ~, a slice given to comparable, and a
# type that lacks the constraint's method.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/satisfy.go" <<'GO'
package main
import "fmt"
type Exact interface{ int | float64 }
type Celsius float64
func Double[T Exact](v T) T { return v * 2 }
func Index[T comparable](values []T, want T) int { return -1 }
type Stringer interface{ String() string }
func Show[T Stringer](v T) string { return v.String() }
func main() {
fmt.Println(Double(Celsius(21)))
fmt.Println(Index([][]int{{1}}, []int{1}))
fmt.Println(Show(42))
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build satisfy.go'
(cd "$dir" && go build satisfy.go) 2>&1
echo "exit status $?"
generics_err_outside_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# An interface that contains a union is a type set, usable only as a
# constraint: not as a parameter type, a variable's type, a slice's element
# type, or an embedded field.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/outside.go" <<'GO'
package main
type Number interface{ ~int | ~float64 }
type Reading struct {
Number
}
func Describe(n Number) string { return "" }
func main() {
var total Number
var history []Number
_, _ = total, history
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build outside.go'
(cd "$dir" && go build outside.go) 2>&1
echo "exit status $?"
generics_err_operator_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# An operator is allowed on a T only if every type in T's set has it. Under any
# nothing is; under ~int | ~string, + is (both have it) and unary - is not.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/operators.go" <<'GO'
package main
func Larger[T any](a, b T) T {
if a > b {
return a
}
return b
}
func Add[T any](a, b T) T { return a + b }
func Same[T any](a, b T) bool { return a == b }
func Concat[T ~int | ~string](a, b T) T { return a + b }
func Negate[T ~int | ~string](a T) T { return -a }
func main() {}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build operators.go'
(cd "$dir" && go build operators.go) 2>&1
echo "exit status $?"
generics_err_typeswitch_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# A value of type parameter type is not an interface value: no type switch and
# no type assertion on it directly. Converting it to any first is allowed, and
# the fixed version prints what the switch found.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/typeswitch.go" <<'GO'
package main
func Kind[T any](v T) string {
switch v.(type) {
case int:
return "int"
}
if _, ok := v.(string); ok {
return "string"
}
return "other"
}
func main() { _ = Kind(1) }
GO
cat >"$dir/typeswitch_fixed.go" <<'GO'
package main
import "fmt"
func Kind[T any](v T) string {
switch any(v).(type) {
case int:
return "int"
case string:
return "string"
}
return "other"
}
func main() { fmt.Println(Kind(1), Kind("pear"), Kind(2.5)) }
GO
say() { printf '$ %s\n' "$*"; }
say 'go build typeswitch.go'
(cd "$dir" && go build typeswitch.go) 2>&1
echo "exit status $?"
say 'go run typeswitch_fixed.go'
(cd "$dir" && go run typeswitch_fixed.go) 2>&1
echo "exit status $?"
generics_err_receiver_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# The names in a receiver's brackets declare type parameters; they never pick
# an instantiation. Stack[int] declares a parameter called int, which shadows
# the predeclared int inside the method.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/receiver.go" <<'GO'
package main
type Stack[T any] struct{ items []T }
func (s *Stack[int]) Sum() int {
total := 0
for _, v := range s.items {
total += v
}
return total
}
func main() {}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build receiver.go'
(cd "$dir" && go build receiver.go) 2>&1
echo "exit status $?"
Sources¶
- The Go specification: Type parameter declarations ↗, Instantiations ↗, Type inference ↗, Satisfying a type constraint ↗, Operators ↗, Type switches ↗, Method declarations ↗.
- The Go FAQ: Why can't I use a more specific type for the receiver of a parameterized type? ↗ and Why can't the compiler infer the type argument in my program? ↗.
- The messages themselves are produced by the type checker in
src/internal/types/errors↗, whosecodes.golists each error code with an example.