Irbisa · Cheatsheet
Generics & Type Parameters
Type parameters, constraints, type sets, inference, GC shape stenciling
What do generics give you over
any(interface{}), and when isanystill the right choice?How do you declare a generic function and a generic type in Go, say
Map,Filterand aStack[T]with methods?func Map[T, U any](xs []T, f func(T) U) []U { out := make([]U, 0, len(xs)) for _, x := range xs { out = append(out, f(x)) } return out…This
Maxdoes not compile. Why, and which constraint fixes it?func Max[T any](a, b T) T { if a > b { return a } return b}How do you return the zero value of a type parameter
Tfrom a generic function, and why donil,0andT{}not work?// Find returns the first match. The bool is the answer; the T is only the payload.func Find[T any](xs []T, ok func(T) bool) (T, bool) { for _, x := range xs { if ok(x) { return x, true }…What does the
~mean in a constraint like~string, and why does[T string]reject atype UserID string?type UserID string // wrong: the type set is exactly {string}; UserID is a different named typefunc FirstBad[T string](xs []T) T { return xs[0] } // right: ~string admits every type whose underlying type is string…Which inputs does Go use to infer type arguments, and why does a constructor like
NewSet()still have to be writtenNewSet[string]()?type Set[T comparable] struct{ m map[T]struct{} } func NewSet[T comparable]() *Set[T] { return &Set[T]{m: map[T]struct{}{}} } func Map[S, T any](xs []S, f func(S) T) []T { out := make([]T, 0, len(xs))…Why can
type Number interface{ ~int | ~float64 }be used only as a constraint and never asvar n Number, and where doescmp.Orderedcome from?type Integer interface { ~int | ~int8 | ~int16 | ~int32 | ~int64} type Float interface{ ~float32 | ~float64 }…CentsandMicrosboth haveString(), yetv.String()does not compile forT interface{ Cents | Micros }. Why, and what may a union term be?type Cents int64type Micros int64 func (c Cents) String() string { return fmt.Sprintf("%d.%02d", c/100, c%100) }func (m Micros) String() string { return fmt.Sprintf("%d.%06d", m/1e6, m%1e6) }…What does this print, and why does the switch need
any(v)rather thanv.(type)?type MyInt int func Describe[T any](v T) string { switch any(v).(type) { case int: return "int" case fmt.Stringer: return "stringer" default: return "other" }} // in main:fmt.Println(Describe(42), Describe(MyInt(42)), Describe(time.Second))Find the bug:
OrDefaultnever falls back todefhere, and the next line panics.// OrDefault returns def when v is unset.func OrDefault[T any](v, def T) T { if any(v) == nil { return def } return v} var cfg *Config // not loaded yetc := OrDefault(cfg, &Config{Timeout: 5 * time.Second})fmt.Println(c.Timeout)Index[T comparable](s []T, v T)accepts a[]any, compiles, then panics withcomparing uncomparable type []int. Why didn'tcomparableprevent it?Find the bug: this typed
sync.Mapwrapper panics the first time each user id is requested.// Cache is a typed wrapper over sync.Map.type Cache[K comparable, V any] struct { m sync.Map} func (c *Cache[K, V]) Put(k K, v V) { c.m.Store(k, v) } func (c *Cache[K, V]) Get(k K) (V, bool) { v, ok := c.m.Load(k) return v.(V), ok} user, ok := users.Get(id)if !ok { user = loadUser(id)}This leaderboard sort returns rows with equal scores in an arbitrary order. What is wrong with the comparison function?
type Row struct { Name string Score int} slices.SortFunc(rows, func(a, b Row) int { if a.Score < b.Score { return -1 } return 1})How do you write a range-over-func iterator with
iter.Seq, what doesyieldreturningfalsemean, and where does cleanup go?// Cleanup belongs to the iterator: it opened the file, so it closes it.func Lines(name string) iter.Seq2[string, error] { return func(yield func(string, error) bool) { f, err := os.Open(name) if err != nil { yield("", err)…When is a type parameter the wrong tool, and how do you choose between generics, an interface and plain concrete types?
Scale[E ~int32](s []E, c E) []Eaccepts yourtype Point []int32but returns a plain[]int32. What signature keepsPoint, and why does the call site not change?type Point []int32 func (p Point) Norm() int32 { var n int32 for _, v := range p { n += v * v…Before Go 1.27 a method could not declare its own type parameters, as in
func (s *Set[T]) Map[U comparable](f func(T) U) *Set[U]. Why, and what changed in Go 1.27?type Set[T comparable] struct{ m map[T]struct{} } func NewSet[T comparable]() *Set[T] { return &Set[T]{m: map[T]struct{}{}} } // fine: the receiver's own parameterfunc (s *Set[T]) Add(v T) { s.m[v] = struct{}{} }…Decode[T Validator]rejectsRowbecauseValidatehas a pointer receiver, andDecode[*Row]unmarshals into a nil pointer. How do you constrain*Tinstead?type Validator interface{ Validate() error } type Row struct{ N int } // pointer receiver: in *Row's method set, not Row'sfunc (r *Row) Validate() error {…How does the Go compiler implement generics (GC-shape stenciling with dictionaries), and when is a generic function slower than the interface version?
type A struct{ p *int }type B struct{ q *string } func First[T any](xs []T) T { return xs[0] } func Sum[T ~int | ~float64](xs []T) T {…