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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
