liveBook · Manning

Generics

As explained in Go in Practice, Second Edition

A generic function is a function declared with type parameters so it can operate across multiple permitted concrete types.

When the same operation must work on several types, a generic function lets you write the operation once. Its type parameter appears in square brackets, and a constraint specifies which types are permitted. This can replace duplicated functions or uses of `interface{}` that provide no guardrails over valid input types. section 3.4

Slice processing is a good fit: traversing a slice and appending selected items does not depend on the element type. A callback can supply the type-specific test, leaving the loop reusable. section 3.4.1

Filter integers with one reusable function

```go package main import "fmt" func filter[T any](items []T, keep func(T) bool) []T { var out []T for _, item := range items { if keep(item) { out = append(out, item) } } return out } func main() { nums := []int{1, 2, 3, 4, 5, 6} fmt.Println(filter[int](nums, func(n int) bool { return n%3 == 0 })) } ``` Run it with `go run generic.go` to print `[3 6]`. Here, `any` permits the same filter to work with other element types; the callback decides what to keep. section 3.4.1

Use a narrower constraint when the function itself needs an operation that only selected types support. A `Numeric` interface listing `int8 | int16 | int32 | int64 | float32 | float64` permits one `filterPositive[T Numeric]` function to test `v > 0` for those types. Generic structs are possible too: `Animal[T AnimalType]` can restrict its stored value to a declared union of types. Type arguments can be written explicitly or, in the shown function call, inferred. section 3.4.1 section 3.4

Listing 3.11 A filter function with specific types
type Numeric interface {
    int8 | int16 | int32 | int64 | float32 | float64
}

func filterPositive[T Numeric](items []T) []T {
    var filtered []T
    for _, v := range items {
        if v > 0 {
            filtered = append(filtered, v)
        }
    }
    return filtered
}


func main() {
    ints := []int8{-4,-3,-2,-1,0,1,2,3,4,}
    ints = filterPositive[int8](ints)
        fmt.Println(ints)

    floats := []float32{-4.5,-3.5,-2.5,-1.5,0.5,1.5,2.5,3.5,4.5,}
    floats = filterPositive[float32](floats)
    fmt.Println(floats)
}

The complete constrained filter applies the same positive-value test to `int8` and `float32` slices. section 3.4.1

Reach for generics when several similar types need the same logic, especially for maps or slices. Ordinary interfaces and type-specific code remain reasonable when they express the task clearly; generics are useful, not obligatory. section 3.4.1 section 3.5

How Go in Practice, Second Edition covers it

How deep it goes
Works through generic structs, function type parameters, inference, and constrained filters without descending into compiler internals.
Examples
Demonstrates filtering capitalized strings and divisible-by-three integers, then retaining positive `int8` and `float32` values.
What it assumes
Builds on Go slices, functions, structs, and interfaces while introducing type parameters and constraints through examples.
What you can do after
Build it from scratch: implement a reusable slice filter or restrict a numeric filter to permitted types.
How it connects
Connects type parameters and constraints to interfaces, structs, slices, and functional filtering.

Also explained in

Compare how 20 books explain generics →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go in Practice, Second Edition. The section links point to the parts of the book it draws on.