Sunday, August 16, 2026
Why Won't My Generic Type Satisfy This Interface in Go?
Posted by

Because a constraint is a set of types, not a fuzzy "looks like." type ID int is not int. A method on *T does not put T in an interface's set. An interface that lists types (int | string, ~int) is a general interface: it constrains type parameters and cannot hold a value. Inside the generic body, T only has operations that every type in the set supports.
The failure
Three compile errors that look like "generics are broken." They are the type set doing its job.
type Integer interface {
int | int64
}
func Add[T Integer](a, b T) T { return a + b }
type ID int
func main() {
Add(ID(1), ID(2)) // ID does not satisfy Integer (int | int64)
}
type Writer interface {
Write([]byte) (int, error)
}
type Buf struct{ p []byte }
func (b *Buf) Write(p []byte) (int, error) {
b.p = append(b.p, p...)
return len(p), nil
}
func Flush[T Writer](w T) {}
func demo() {
var b Buf
Flush(b) // Buf does not implement Writer (Write has pointer receiver)
}
var x Integer = 1 // interface contains type constraints
Why this happens
Defined types are new types. ID has underlying type int but is not int. A union of named types lists those names only. ~int means "any type whose underlying type is int," which includes ID.
Method sets. Writer is the set of types that have Write. *Buf has it. Buf does not. Instantiating Flush[Buf] asks whether Buf is in that set. No. Flush(&b) or Flush[*Buf] is.
Basic vs general. Method-only interfaces are values (io.Writer). Interfaces with type elements (int | string, ~int) exist to describe a set for the compiler. There is no runtime box that can be "an int or a string" with + dispatched. So var x Integer is illegal.
Intersection. If the constraint is io.Reader | io.Writer, a value of type T is one or the other. You cannot call Read on T because a Writer in the set has no Read. The compiler only allows methods (and operators) present on all members.
The fix
Use ~ for defined numeric/string types:
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
func Add[T Integer](a, b T) T { return a + b }
type ID int
_ = Add(ID(1), ID(2))
Match the receiver the methods actually use:
Flush(&b)
// or
func Flush[T Writer](w T) {}
Flush[*Buf](&b)
Keep unions as constraints. For a runtime value, use a basic interface (io.Writer) or a concrete type.
Need both a type set and methods? Intersect them in one constraint:
type NamedInt interface {
~int
fmt.Stringer
}
int fails (no String). type Label int with func (Label) String() string succeeds.