Skip to content

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.

toLabel := Map[int, string]
var invoices Stack[string]
var entry Pair[string, float64]

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