Ramblings of an aging IT geek
← Ramblings of an aging IT geek
golang

a year on, where go generics actually earn their keep

After a year with generics in Go, the few places I reach for them and the many places interfaces still win.

Code on a screen

Generics landed in Go a little over a year ago now, and the early excitement has settled into something more useful: a sense of where they actually belong. My honest answer to "do I even need them" is mostly no, occasionally yes, and the yes cases are narrower and more boring than I expected. Which, for Go, is a compliment.

The place they unambiguously earn their keep is small, type-safe container and collection helpers. The Map, Filter, Keys, Values family that everyone reimplements in every codebase. Before, you either wrote them per-type and copied the boilerplate, or you went through interface{} and lost the compiler's help at exactly the moment you wanted it. Now you write it once.

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
}

That is genuinely nicer than the three alternatives it replaces, and golang.org/x/exp/slices and maps give you most of the common ones already, so you often do not even write your own.

Programming on a laptop

The second real win is numeric code with a constraint. A Sum or a Max that works across all the integer and float types, expressed with a constraint rather than a stack of near-identical functions. Constraints are the part of the feature I underrated: a constraint is just an interface listing the types or methods you will accept, and writing constraints.Ordered instead of an interface{} and a runtime type switch feels like the language finally letting you say what you meant.

Where I have learned not to reach for them is anything that already had a clean interface. Go's interfaces were always its generics for behaviour, and they still are. If I want to accept "anything I can read from", io.Reader is the answer, not a type parameter. Generics are for when you are parameterising over a type you do not care about the behaviour of, and that turns out to be a much rarer situation than the container examples make it look.

I have also watched generics turn perfectly readable code into a soup of bracketed type parameters when someone got excited. A function signature with three type parameters and two constraints is not progress, it is a puzzle. The good uses are the ones where the generic version reads more simply than the thing it replaced, and if it does not, that is the signal to stop.

So: do I need them? Rarely. Am I glad they exist? Yes, for the dozen lines of collection helpers I no longer copy between projects. That is a small thing to be glad about, and it is exactly the kind of small, dull, useful that suits the language.