The Franconian
Coder Studio

Generics in Go:

Generics in Go:
Tradeoffs and Alternatives

Go’s generics prioritize simplicity over flexibility—but how do they stack up against Rust, TypeScript, and code generation? I explore the compile-time vs. runtime tradeoffs of each approach.

For three years now, Go has supported generics. True to the language’s ethos, simplicity was a key priority. For instance, generic methods on non-generic types remain unsupported. Yet generics offer a crucial advantage: compile-time specialization eliminates runtime overhead entirely.

Rust and TypeScript: More Power, More Complexity

In contrast, Rust has had generics from nearly the beginning. Its type constraints are far more powerful than Go’s—though this also makes the feature significantly more complex. Similarly, TypeScript supports generics and surpasses Go in flexibility. However, they serve purely as a development aid here, with no specialized code generated.

Pre-Generics Era: The Cost of interface and Reflection

In the pre-generics era, Go relied heavily on empty interfaces (interface{}, now any) for generic behavior. Type checks happened at runtime via assertions or switches, often leading to errors if omitted. Boxing values into interface{} also triggered heap allocations, ruling out compile-time optimizations.

The same limitations apply to reflection, though with even greater overhead. Yet reflection remains relevant for truly generic operations.

Interfaces as a Middle Ground

Interfaces can be seen as a graduated generic alternative. They define required functions, eliminating the need for type checks. While the overhead is reduced, it’s still present: runtime lookups for concrete implementations (dynamic dispatch) are required, and values often still end up on the heap.

Code Generation: Beyond Generics

Code generation, however, can surpass generics entirely. When language features fall short, specialized generators produce highly efficient, zero-overhead code. Tools like sqlc exemplify this brilliantly:

Compile SQL to type-safe code; catch failures before they happen.

This approach is ingenious. Unlike generics, it emits specialized functions as actual code—not just during compilation. You see the functions and work with them directly. If the input changes, the impact is immediate. Here, tools like go generate—often combined with goyacc or simple templating—are used. Another well-known example is stringer.

The Verdict: Right Tool for the Job

Go’s generics offer a simple, performant solution—though they lack the flexibility of Rust’s. Yet alternatives like interfaces and code generation let developers tackle even complex requirements efficiently. Code generation stands out most: it delivers maximum control and transparency, perfectly aligning with Go’s philosophy of simplicity and efficiency.

#golang#generics#rust#typescript#code generation#performance
Read more in Languages & Runtimes!