How to Use Generics in Go to Eliminate sync.Pool Boxing Overhead

This article explains why the pre‑generic sync.Pool suffers from boxing and type‑assertion costs, shows how to wrap it with a type‑safe generic Pool[T] implementation, demonstrates usage with a byte‑buffer example, and outlines the three main benefits and caveats of the approach.

Golang Shines
Golang Shines
Golang Shines
How to Use Generics in Go to Eliminate sync.Pool Boxing Overhead

1. Pain points before generics

Traditional sync.Pool stores values as any (alias interface{}). When Get() is called, two performance costs appear:

Boxing overhead : placing a primitive or struct into any may cause an extra heap allocation.

Assertion overhead : after retrieving an object you must assert its concrete type (e.g., obj.(*MyStruct)), which adds runtime cost and can panic on failure.

2. Generic encapsulation

Using Go generics a type‑safe wrapper Pool[T] can be built around sync.Pool, fixing the type at compile time.

Code implementation: GenericPool

package main

import (
    "fmt"
    "sync"
)

type Pool[T any] struct {
    internal sync.Pool
}

func NewPool[T any](alloc func() T) *Pool[T] {
    return &Pool[T]{
        internal: sync.Pool{
            New: func() any { return alloc() },
        },
    }
}

func (p *Pool[T]) Get() T { return p.internal.Get().(T) }

func (p *Pool[T]) Put(x T) { p.internal.Put(x) }

type Buffer struct { Data []byte }

func main() {
    bufPool := NewPool[*Buffer](func() *Buffer {
        fmt.Println("creating new object")
        return &Buffer{Data: make([]byte, 1024)}
    })
    b := bufPool.Get()
    b.Data[0] = 255
    fmt.Printf("Buffer length: %d
", len(b.Data))
    bufPool.Put(b)
}

3. Core benefits of generic pools

A. Improved developer experience

IDE auto‑completion works with the concrete type, eliminating the need to guess whether an any value is *User or *Order.

B. Elimination of hidden performance loss

Although the native sync.Pool handling of any is efficient, in high‑frequency concurrent scenarios the generic wrapper reduces runtime type‑check instructions, tightening the code path.

C. Enforced reset contract

The pool can be extended to require a reset function before Put, preventing data contamination under concurrency.

func (p *Pool[T]) Put(x T, reset func(T)) {
    if reset != nil { reset(x) }
    p.internal.Put(x)
}

4. Caveats

Avoid over‑encapsulation : for a very small, local use‑case the raw sync.Pool with a simple assertion may be more straightforward.

Pointer vs. value : store pointer types (e.g., *MyStruct) in a generic pool. Storing value types still triggers escape analysis to the heap, so GC pressure is not fully avoided.

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 safetysync.Poolboxing
Golang Shines
Written by

Golang Shines

We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.

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.