iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Generics

Go 1.18 added generics — type parameters on functions and types. The most common use cases are container-style helpers (Map, Filter, Reduce), data structures (Set, OrderedMap), and abstractions over numeric types. Don’t reach for them when an interface is clearer.

Type parameters, constraints, inference

EXAMPLE
package main

import (
    "cmp"
    "fmt"
    "slices"
    "strings"
)

// 1) Generic function — Map
func Map[T, U any](in []T, f func(T) U) []U {
    out := make([]U, len(in))
    for i, v := range in { out[i] = f(v) }
    return out
}

func main() {
    nums := []int{1, 2, 3}
    squares := Map(nums, func(n int) int { return n * n })
    fmt.Println(squares)                                // [1 4 9]

    upper := Map([]string{"a", "b"}, strings.ToUpper)
    fmt.Println(upper)                                  // [A B]
}

// 2) Constraints — limit which types are allowed
type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
    ~float32 | ~float64
}

func Sum[T Numeric](xs []T) T {
    var total T
    for _, x := range xs { total += x }
    return total
}

func main2() {
    fmt.Println(Sum([]int{1, 2, 3}))             // 6
    fmt.Println(Sum([]float64{1.5, 2.25}))       // 3.75
}

// ~int means 'int OR any type whose underlying type is int' (type MyID int).

// 3) Built-in constraints from the 'cmp' and 'constraints' packages
func Max[T cmp.Ordered](a, b T) T {
    if a > b { return a }
    return b
}

func main3() {
    fmt.Println(Max(1, 2))                 // 2
    fmt.Println(Max("alpha", "beta"))      // beta
    fmt.Println(Max(3.14, 2.71))           // 3.14
}

// 4) Multi-type parameters
func Pair[K, V any](k K, v V) struct{ Key K; Value V } {
    return struct{ Key K; Value V }{k, v}
}

func main4() {
    p := Pair("id", 42)
    fmt.Println(p.Key, p.Value)              // id 42
}

// 5) Generic types — Stack
type Stack[T any] struct {
    data []T
}

func (s *Stack[T]) Push(v T)      { s.data = append(s.data, v) }
func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.data) == 0 { return zero, false }
    n := len(s.data) - 1
    v := s.data[n]
    s.data = s.data[:n]
    return v, true
}
func (s *Stack[T]) Len() int { return len(s.data) }

func main5() {
    s := &Stack[int]{}
    s.Push(1); s.Push(2); s.Push(3)
    for s.Len() > 0 {
        v, _ := s.Pop()
        fmt.Println(v)                          // 3 2 1
    }
}

// 6) Set built on map
type Set[T comparable] struct {
    m map[T]struct{}
}

func NewSet[T comparable](xs ...T) *Set[T] {
    s := &Set[T]{m: make(map[T]struct{}, len(xs))}
    for _, x := range xs { s.m[x] = struct{}{} }
    return s
}

func (s *Set[T]) Add(v T)            { s.m[v] = struct{}{} }
func (s *Set[T]) Has(v T) bool       { _, ok := s.m[v]; return ok }
func (s *Set[T]) Remove(v T)         { delete(s.m, v) }
func (s *Set[T]) Len() int           { return len(s.m) }

func main6() {
    s := NewSet(1, 2, 3)
    s.Add(2)
    fmt.Println(s.Has(2), s.Len())              // true 3
}

// 7) Filter + Reduce
func Filter[T any](xs []T, pred func(T) bool) []T {
    out := make([]T, 0, len(xs))
    for _, x := range xs {
        if pred(x) { out = append(out, x) }
    }
    return out
}

func Reduce[T, U any](xs []T, init U, f func(U, T) U) U {
    acc := init
    for _, x := range xs { acc = f(acc, x) }
    return acc
}

func main7() {
    nums := []int{1, 2, 3, 4, 5}
    evens := Filter(nums, func(n int) bool { return n % 2 == 0 })
    sum   := Reduce(nums, 0, func(a, b int) int { return a + b })
    fmt.Println(evens, sum)                       // [2 4] 15
}

// 8) Type inference
// Go infers type parameters from arguments when it can.
Max(1, 2)                                          // T inferred as int
Max[string]("a", "b")                              // explicit, sometimes needed
// You must spell out the type when:
//   • Only the return type uses T (no argument constrains it)
//   • Constraint inference can't decide

// 9) The standard library has generic packages
import (
    "slices"
    "maps"
)

s := []int{3, 1, 2}
slices.Sort(s)                                     // [1 2 3]
slices.Contains(s, 2)                              // true
idx, _ := slices.BinarySearch(s, 2)                 // 1

m := map[string]int{"a": 1, "b": 2}
keys := maps.Keys(m)                                 // iterator (Go 1.23+)
vals := maps.Values(m)

// 10) Constraints + methods
type Stringer[T any] interface {
    String() string
    Equal(other T) bool
}

func Print[T Stringer[T]](items []T) {
    for _, it := range items { fmt.Println(it.String()) }
}

// 11) When to use generics
//   • Containers / data structures over any element type
//   • Numeric helpers that should work across int/float
//   • Algorithms (Sort, BinarySearch, Min) that don't need behaviour from the type
//   • Type-safe option / Result wrappers

// 12) When NOT to use generics
//   • A single method call — pass an interface instead
//   • The function uses runtime type assertions internally — likely the wrong abstraction
//   • You're trying to emulate inheritance — Go's composition is the answer
//   • The constraint becomes more complex than the function body — collapse to interface{}

// 13) Common bugs
//   • Forgot ~ in the constraint — type MyID int won't satisfy 'int' alone
//   • Generic methods aren't allowed (only generic functions and types)
//   • Type inference fails — supply the type parameter explicitly
//   • Embedding constraint inside another generic — readability gets ugly fast
//   • Re-implementing slices.Sort / slices.Contains because you didn't know the stdlib package existed
//   • Comparing zero value with == on a 'any' type parameter — use reflect or a comparable constraint

Why it matters

Generics in Go are best for containers (Map, Set, Stack), numeric helpers, and standard-library style algorithms like slices.Sort. Don’t reach for them when an interface would do — if you need behavior from the type, an interface is usually clearer than a constrained type parameter.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
func Map[T, U any](xs []T, f func(T) U) []U {
    out := make([]U, len(xs))
    for i, x := range xs { out[i] = f(x) }
    return out
}
Try it Yourself »

Discussion

Loading…