Type-safe object reuse for Go, with control over what survives garbage collection.
Reuse buffers, encoders, and scratch objects without repeating type assertions
or reset logic at every call site. objpool provides three small building blocks:
Pool[T]wrapssync.Pool.Getreturns a ready*T, never nil.FreeList[T]keeps idle objects across garbage collections, with an optional limit on how many it retains.RatchetTrimhelps you retire objects whose buffers stayed oversized after a burst of traffic.
Both pools support concurrent callers. The library uses only the Go standard library at runtime.
Quick start · Choosing a pool · Usage guide · Benchmarks · Contributing
Requires Go 1.18 or later. Inside your Go module:
go get github.com/JohanLindvall/objpoolSave this as main.go, then run go run .:
package main
import (
"bytes"
"fmt"
"github.com/JohanLindvall/objpool"
)
func main() {
buffers := objpool.New(
func() *bytes.Buffer { return new(bytes.Buffer) },
func(b *bytes.Buffer) error { b.Reset(); return nil },
)
buf := buffers.Get()
defer buffers.Put(buf)
buf.WriteString("hello, objpool")
fmt.Println(buf.String())
}Output:
hello, objpool
Create the pool once and share it across operations. Each operation gets an
object, uses it, then returns it exactly once. Recycled objects are reset by
Get, so every caller receives a ready object.
The executable examples also cover reuse across garbage
collections, compression writers, and trimming. Run them from a checkout with
go test -run Example -v ./....
| Need | Use | Tradeoff |
|---|---|---|
| Reuse temporary objects that are cheap to rebuild | Pool[T] |
The runtime may discard idle objects at any time. |
| Keep expensive objects warm across garbage collections | FreeList[T] |
Idle objects retain memory until reused or the list becomes unreachable. |
| Drop buffers that stay oversized after a burst | Either pool + RatchetTrim |
Your release code decides whether to return the object. |
Pool[T] has the same retention contract as
sync.Pool. Its purpose is typed access and
centralized initialization and reset.
FreeList[T] uses a mutex-protected stack and returns the most recently returned
object. Set maxIdle to limit idle object count; it does not limit concurrent
checkouts or total memory:
buffers := objpool.NewFreeList(64,
func() *bytes.Buffer { return bytes.NewBuffer(make([]byte, 0, 1<<20)) },
func(b *bytes.Buffer) error { b.Reset(); return nil },
)Here, up to 64 idle buffers keep their capacity across garbage collections.
maxIdle <= 0 means no idle limit. A buffer can grow beyond its initial 1 MiB;
see the memory and trimming guide.
Profile before adding a pool: allocating a small, short-lived value may already be inexpensive. These are reuse pools for in-memory objects; they do not provide connection management, cleanup callbacks, or a concurrency limit.
- Build ready objects.
newFnmust return a ready, non-nil*T. A nil constructor function panics immediately; a nil result panics duringGet. - Reset on reuse.
resetFnruns duringGetonly for recycled objects. Passnilif no reset is needed. A reset error discards the object and builds a replacement; the error is not returned to the caller. - Own one checkout at a time. Stop using an object and anything that aliases
its storage after
Put. Return each checkout at most once.Put(nil)is safe. - Keep callbacks safe for concurrent calls. Pools synchronize storage; callbacks run on the calling goroutine and can run concurrently for different objects. The objects themselves are not made safe for concurrent use.
- Use the constructors. Pool zero values are not usable. Do not copy a pool after first use.
Put does not clear contents. If data must be cleared immediately, do that
before returning the object. The usage guide explains ownership,
reset errors, caller-supplied reset arguments, and memory retention.
| Symbol | Purpose |
|---|---|
New[T](newFn, resetFn) |
Create a typed Pool[T]. |
NewFreeList[T](maxIdle, newFn, resetFn) |
Create a FreeList[T] with an optional idle limit. |
Get() *T / Put(*T) |
Check out a ready object / return it for reuse. |
DefaultRatchetTrim() |
Create a policy with a 16 MiB floor, factor 4, and 8 strikes. |
(*RatchetTrim).Due(retained, used) |
Record one use cycle and decide whether to drop the object. |
See Go Reference for full signatures, contracts, and examples.
The repository includes allocation, sync.Pool, Pool, and FreeList
benchmarks with both serial and parallel workloads:
go test -run '^$' -bench . -benchmem ./...Use the benchmark guide to reproduce results and compare changes. Throughput and memory savings depend on object size, concurrency, and garbage collection frequency.
Questions, bug reports, documentation fixes, and realistic benchmark cases are welcome. Open an issue or read the contribution guide to get started.
Please follow the code of conduct and use the security policy for private vulnerability reports.
Maintained by Johan Lindvall. If objpool helps your project, a star helps other Go developers discover it.
Version tags are available to Go
modules. CI tags each passing commit on main with the next patch version;
minor and major versions are chosen by the maintainer. During v0, APIs may
change between releases, so review changes when updating.
Released under the MIT license.