Why Go’s Generics Feel Slow: A Deep Dive into gcshape and Real‑World Pitfalls

The article reviews a year of experience with Go generics, explains the gcshape implementation, shows why generic code can be slower than ordinary code, and provides practical advice on object creation, pointer handling, method sets, and copying with concrete examples and benchmarks.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Why Go’s Generics Feel Slow: A Deep Dive into gcshape and Real‑World Pitfalls

How Go Implements Generics

Go does not use the classic C++ template instantiation, TypeScript/Java type erasure, or C#'s hybrid approach; instead it introduces a concept called gcshape. Types that share the same underlying type belong to the same shape, and the compiler generates one copy of code per shape.

Performance Problems

Benchmarks comparing generic functions, generic functions that use interfaces, and ordinary code show that pure generic code is only about 10% slower, while generic‑plus‑interface code can be up to 100% slower. The slowdown comes from the extra lookup of the type dict for a type parameter and the need to convert the generic value to its concrete type, which adds a pointer indirection and a copy.

func Output[T any]() {
    var t T
    fmt.Printf("%#v
", t)
}

type A struct { a,b,c,d,e,f,g int64; h,i,j string; k []string; l,m,n map[string]uint64 }

type B A

func main() {
    Output[string]()
    Output[int]()
    Output[uint]()
    Output[int64]()
    Output[uint64]()
    Output[*string]()
    Output[*int]()
    Output[*uint]()
    Output[*A]() // all pointers share the same shape
    Output[A]()
    Output[*B]()
    Output[B]() // B shares code with A because their underlying types are identical
    Output[[]int]()
    Output[*[]int]()
    Output[map[int]string]()
    Output[*map[int]string]()
    Output[chan map[int]string]()
}

The article also shows a benchmark that measures the cost of calling generic functions versus non‑generic ones, confirming the performance penalty.

Creating Objects Inside Generic Functions

Because a type parameter T may not be directly instantiated, the only safe way to create a zero value is var ret T. Using new(T) or T{} can cause compile‑time errors for types that do not support those operations.

func F[T any]() T {
    var ret T // works for any T
    return ret
}

If the constraint includes methods, you must still use a concrete pointer type in the signature, e.g. func Set[T int|uint](ptr *T), or introduce a second type parameter that represents a pointer to T.

Method Sets and Type Parameters

Methods defined on concrete types are not automatically available on a type parameter, even if the parameter’s underlying type matches. To call a method like Hello() on a generic value you must either constrain the parameter with an interface that contains the method or use a second pointer‑type parameter that embeds *T and the required methods.

type API[T any] interface { *T; Hello() }

func SayHello[T A|B, PT API[T]](a PT) {
    a.Hello()
}

Copying Objects

Shallow copies work with the usual assignment b := a. For pointers wrapped in a type parameter you need to dereference the generic pointer, copy the value, and then re‑wrap it:

func DoCopy[T any, PT API[T]](a PT) {
    b := *a          // shallow copy of the underlying value
    (PT(&b)).Set(222222) // modify through the generic pointer
    fmt.Println(a, b)
}

Deep copies require an explicit Clone() method defined in an interface such as Cloneable[T any] interface { Clone() T }.

Summary and Best Practices

After a year of using Go generics the author identifies three main pain points: pointer handling, performance overhead when methods are involved, and the need to understand the core type of a constraint. Recommended practices include:

Prefer using *T explicitly instead of letting T stand for a pointer.

Use []T and map[T1]T2 directly rather than letting T represent a slice or map.

Favor generic structs over generic functions when possible.

Be aware that the core type of a constraint determines which operations are allowed.

For now, the safest approach is to rely on well‑tested generic libraries for slices and maps, and avoid more exotic generic tricks that involve pointer creation or method calls on type parameters.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceGoGenericsType Parametersgcshape
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.