Generic Limitations — Find the Bug¶
How to use¶
Each problem shows a code snippet that the compiler refuses. Read it carefully and answer: 1. What limit is being violated? 2. What is the exact compile-error wording (or close to it)? 3. What is the canonical workaround?
Solutions are at the end.
Bug 1 — method type parameter¶
type Box[T any] struct{ V T }
func (b Box[T]) Map[U any](f func(T) U) Box[U] {
return Box[U]{V: f(b.V)}
}
Hint: The error is about methods declaring something they shouldn't.
Bug 2 — type-switch on T¶
func Describe[T any](v T) string {
switch v.(type) {
case int: return "int"
case string: return "str"
}
return "?"
}
Hint: What kind of operand does a type switch require?
Bug 3 — covariant slice assignment¶
type Animal interface{ Name() string }
type Cat struct{}
func (Cat) Name() string { return "cat" }
func main() {
cats := []Cat{{}, {}}
var animals []Animal = cats
_ = animals
}
Hint: Variance rule.
Bug 4 — len on bare T¶
Hint: What types is len defined for?
Bug 5 — make on bare T¶
Hint: What does make need to know?
Bug 6 — interface with method type parameter¶
Hint: Same rule as concrete methods.
Bug 7 — receiver missing type parameter¶
Hint: The receiver must mention the type parameter.
Bug 8 — ~ on an interface¶
Hint: ~T requires a specific kind of type.
Bug 9 — interfaces in a union¶
Hint: Type elements have rules about what can appear.
Bug 10 — embedding a type parameter¶
Hint: What can a struct embed?
Bug 11 — comparing distinct instantiations¶
Hint: Are Box[int] and Box[int64] the same type?
Bug 12 — generic constant¶
Hint: What is special about constants?
Bug 13 — type assertion on T¶
Hint: Same rule as type switches.
Bug 14 — pre-1.24 generic alias¶
Hint: Aliases vs definitions.
Bug 15 — generic type alias used as defined type pre-1.24¶
// Pre-1.24 with workaround
type Vec[T any] []T
func main() {
v := []int{1, 2, 3}
var x Vec[int] = v
_ = x
}
Hint: Workaround changed the semantics. Does this assignment compile?
Solutions¶
Bug 1 — fix¶
Error: method must have no type parameters (or similar). Methods cannot declare new type parameters. Lift to a free function:
Bug 2 — fix¶
Error: cannot use type switch on type parameter value v (T is not an interface). Funnel through any:
Bug 3 — fix¶
Error: cannot use cats (variable of type []Cat) as []Animal value. Slices are invariant. Copy element-by-element:
Bug 4 — fix¶
Error: invalid argument: v (variable of type T constrained by any) for len. Add a constraint that supports len:
Bug 5 — fix¶
Error: cannot make T: T does not have core type slice/map/chan. make needs a slice/map/chan kind. Rewrite with explicit slice:
Bug 6 — fix¶
Error: interface method must have no type parameters. Use a free function:
Bug 7 — fix¶
Error: instantiation cycle or missing type argument list in type Box. The receiver must repeat the type parameter list:
Bug 8 — fix¶
Error: cannot use ~S (S is an interface type). Use a concrete underlying type:
Bug 9 — fix¶
Error: cannot use fmt.Stringer in union (Stringer is an interface). Type elements in unions must be concrete types. Workaround: define a method requirement instead, or accept any and handle dynamically.
Bug 10 — fix¶
Error: embedded field type cannot be a type parameter. Make it a named field:
Bug 11 — fix¶
Error: invalid operation: a == b (mismatched types Box[int] and Box[int64]). Different instantiations are different types. Convert explicitly:
Bug 12 — fix¶
Error: T is not a valid constant type. Constants cannot have a type-parameter type. Use a variable:
Bug 13 — fix¶
Error: cannot use type assertion on type parameter value. Funnel through any:
Bug 14 — fix¶
Error in 1.22: type alias must have no type parameters (the spec rejected it pre-1.24). Use a type definition:
14-generic-type-aliases for the full story. Bug 15 — fix¶
Error: cannot use v (type []int) as Vec[int] value. The type definition created a distinct named type. Convert explicitly:
type Vec[T any] = []T, the assignment would be implicit because the alias preserves identity. Lessons¶
Patterns from these bugs:
- Methods cannot have new type parameters (Bugs 1, 6). The receiver's type parameters are the only ones in scope.
- Type-switch and type-assertion need interface operands (Bugs 2, 13). Funnel through
any. - Variance is invariance (Bug 3). Copy element-by-element.
- Predeclared functions on T require constraint guarantees (Bugs 4, 5). Add the constraint.
- Type elements have strict rules (Bugs 8, 9). No
~Interface, no interfaces in unions. - Embedding requires a defined type (Bug 10). Use a named field.
- Each instantiation is a distinct type (Bug 11). Different parameters → different types.
- Constants cannot have type-parameter types (Bug 12). Use variables.
- Generic type aliases shipped in 1.24 (Bugs 14, 15). Use type definitions on older versions.
- Receivers must include the type parameter list (Bug 7). Always repeat
[T].
A senior engineer reads these errors as signposts: each one points at a documented limit, and each limit has a known canonical workaround. Memorizing the error wording shortens the cycle from "the code does not compile" to "I know exactly what to write instead".