DEV Community

Cover image for Go 1.27 Generic Methods: A Cheat Sheet for Functions, Types & Methods
jjpinto
jjpinto

Posted on

Go 1.27 Generic Methods: A Cheat Sheet for Functions, Types & Methods

๐Ÿงฉ 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)}
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

โœ… 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 infers T from 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
Enter fullscreen mode Exit fullscreen mode

๐Ÿง  Note: The type parameter T is bound to the receiver type Box[T]. Every receiver method on Box[T] automatically gains access to T without 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

๐Ÿ” Deep Dive: Seamless Type Inference: Notice how res.Transform(func(n int) string { ... }) doesn't require explicit call-site parameterization like res.Transform[string](...). Go's inference engine inspects the signature of the closure passed into Transform to infer U = string directly.


โš ๏ธ 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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿงช 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)
Enter fullscreen mode Exit fullscreen mode

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?

ย 
ย 
ย 

This post was reviewed with AI assistance to refine clarity and structure

Top comments (0)