๐งฉ Part 3 of the Idiomatic Go Series โ see Part 1: The Power of Idiomatic Go and Part 2: Go Naming Cheat Sheet
Generics in Go have come a long way since 1.18. Type inference got smarter, the standard library absorbed slices, maps, and cmp, and call-site noise mostly disappeared. Now, with Go 1.27, the final major piece lands, and it's the headline language change of the release: generic methods, methods that can declare their own standalone type parameters, even when the receiver type is not generic.
This cheat sheet covers all three levels of Go generics โ functions, types, and generic methods โ with practical examples, real-world stdlib patterns, and key gotchas to keep in mind.
Quick Reference Card
These three patterns form the backbone of Goโs new expressive power โ without sacrificing simplicity.
// 1. Generic Function
func PrintAnything[T any](v T) { fmt.Println(v) }
// 2. Generic Type
type Box[T any] struct { item T }
// 3. Generic Method (New in Go 1.27)
func (r Result[T]) Transform[U any](f func(T) U) Result[U] {
return Result[U]{val: f(r.val)}
}
The Big Picture
| Level | Syntax Pattern | Idiomatic Use Case |
|---|---|---|
| Generic Function | func Fn[T constraint](v T) T |
Standalone utility routines (slices, cmp, algorithms) |
| Generic Type | type Container[T any] struct { v T } |
Reusable data structures (Box[T], generic queues, caches) |
| Generic Method (Go 1.27) | func (r Result[T]) Transform[U any](f func(T) U) Result[U] |
Method-level transformations & operations on concrete types |
๐ Generic Functions
A function that works across multiple types without code duplication.
func PrintAnything[T any](value T) {
fmt.Println(value)
}
// Usage โ Go's type inference handles the call-site
PrintAnything("hello")
PrintAnything(42)
PrintAnything(3.14)
โ Tip: Type inference improvements in Go 1.21+ and 1.27 mean you rarely need to explicitly specify type arguments like
PrintAnything[string]("hello"). The compiler automatically infersTfrom the passed arguments.
๐ Generic Types
A type (such as a struct) that holds different data types based on how it is declared.
type Box[T any] struct {
item T
}
func (b Box[T]) Item() T {
return b.item
}
// Usage
b1 := Box[int]{item: 10}
b2 := Box[string]{item: "hello"}
fmt.Println(b1.Item()) // 10
fmt.Println(b2.Item()) // hello
๐ง Note: The type parameter
Tis bound to the receiver typeBox[T]. Every receiver method onBox[T]automatically gains access toTwithout needing to redeclare it.
๐ Generic Methods (New in Go 1.27)
A method that can declare its own standalone type parameters โ even if the receiver type is non-generic or requires a different type parameter. This is the headline addition in Go 1.27.
Example A: Non-Generic Receiver with Generic Method
type Player struct {
name string
}
// Method declares its own type parameter P
func (p Player) Say[P any](thing P) {
fmt.Println(p.name, "says:", thing)
}
// Usage
p := Player{name: "Mario"}
p.Say("hello") // Mario says: hello
p.Say(123) // Mario says: 123
p.Say(true) // Mario says: true
Example B: Transforming Types with Transform
Combining generic receiver types with new method type parameters enables smooth functional chaining:
type Result[T any] struct {
val T
}
// The method declares its own new type parameter U
func (r Result[T]) Transform[U any](f func(T) U) Result[U] {
return Result[U]{val: f(r.val)}
}
// Usage
res := Result[int]{val: 100}
halved := res.Transform(func(n int) int { return n / 2 })
summary := halved.Transform(func(n int) string {
return fmt.Sprintf("result=%d", n)
})
fmt.Println(summary.val) // result=50
๐ Deep Dive: Seamless Type Inference: Notice how
res.Transform(func(n int) string { ... })doesn't require explicit call-site parameterization likeres.Transform[string](...). Go's inference engine inspects the signature of the closure passed intoTransformto inferU = stringdirectly.
โ ๏ธ The Gotcha: Generic Methods and Interfaces
Interface methods cannot declare type parameters, and generic methods cannot satisfy interfaces.
type Mapper interface {
Map[U any](f func(int) U) any
}
// compile error: interface method must have no type parameters
This is a deliberate limit: Go resolves interface satisfaction at compile time, and open-ended generic dispatch would clash with that. If you need interface polymorphism across generic types, put the type parameters on the interface instead:
type Mapper[T, U any] interface {
Map(f func(T) U) U
}
๐งช Real-World Example: math/rand/v2
The standard library directly leverages generic methods in Go 1.27. For instance, math/rand/v2 equips (*Rand).N as a generic method operating on integer types, directly matching top-level helper routines:
r := rand.New(rand.NewPCG(1, 2))
fmt.Println(r.N(100)) // int in [0, 100)
Prior to Go 1.27, this call pattern required separate package-level functions. Now, it integrates directly onto the Rand concrete receiver type.
๐งต Wrapping Up
Go generics now cover the full lifecycle: functions that adapt, types that configure, and methods that extend. Go 1.27's generic methods resolve a long-standing language gap while keeping compilation fast and type safety robust.
๐ฌ How are you planning to use generic methods in your Go 1.27 projects? Are you refactoring package-level utility functions into methods on concrete types?
ย
๐ Want to go deeper?
ย
ย
ย
Top comments (0)