What Are Generic Interfaces in Go?
Generic interfaces combine Go's interface type with type parameters introduced in Go 1.18. They allow an interface to express constraints on the underlying type while still supporting polymorphic behavior.
- Interface with type parameters: Declared using square brackets, e.g.,
type Comparable[T any] interface{ Compare(T) int }. - Constraint: The type parameter can be restricted to a set of types (e.g.,
~int | ~float64) or to another interface. - Purpose: Provide compile‑time type safety while keeping the flexibility of traditional interfaces.
How to Use Generic Interfaces in Go
Follow these steps to define and apply generic interfaces in your Go code.
- 1. Define the generic interface. Include a type parameter list and specify the required methods.
type Mapper[T any, R any] interface{ Map(T) R } - 2. Implement the interface. Any concrete type that provides the listed methods satisfies the interface automatically.
type StringLength struct{} func (StringLength) Map(s string) int { return len(s) } - 3. Use the generic interface as a function parameter or return type.
func Apply[T any, R any](m Mapper[T,R], v T) R { return m.Map(v) } - 4. Invoke with concrete types.
var ml Mapper[string,int] = StringLength{} result := Apply(ml, "hello") // result == 5 - 5. Leverage type constraints for richer behavior. Use built‑in constraints like
constraints.Orderedor custom ones to restrict acceptable types.
Why Use Generic Interfaces?
Generic interfaces address several limitations of pre‑generics Go code.
- Enhanced type safety: Errors are caught at compile time rather than at runtime via type assertions.
- Reduced boilerplate: One generic definition replaces multiple duplicated concrete interfaces.
- Improved readability: Intent is expressed directly in the interface signature.
- Better performance: No need for
interface{}conversions or reflection, leading to zero‑cost abstractions. - Future‑proof design: Code written with generic interfaces adapts easily to new types without refactoring.