Generate allocation-free Reset() methods for your structs. Perfect for sync.Pool usage.
This project provides two tools:
| Tool | Description |
|---|---|
resetgen |
Code generator — creates Reset() methods from struct tags |
resetgen-analyzer |
Static analyzer — detects missing Reset() calls before sync.Pool.Put() |
go install github.com/flaticols/resetgen@latestOr add as a tool dependency (Go 1.24+):
go get -tool github.com/flaticols/resetgen@latestGo 1.24 introduced the ability to manage CLI tools as dependencies. You can declare tool requirements in go.mod:
tool (
github.com/flaticols/resetgen
)Run with go tool:
# Generate from current package
go tool resetgen
# Generate from specific packages
go tool resetgen ./...
go tool resetgen ./cmd ./internal
# With flags
go tool resetgen -structs User,Order ./...
go tool resetgen -versionThis approach keeps your tool versions synchronized with your project, just like regular dependencies.
Add reset tags to your struct fields and run the generator:
//go:generate resetgen
package main
type Request struct {
ID string `reset:""`
Method string `reset:"GET"`
Headers map[string]string `reset:""`
Body []byte `reset:""`
}Run:
go generate ./...Generated request.gen.go:
func (s *Request) Reset() {
s.ID = ""
s.Method = "GET"
clear(s.Headers) // preserves capacity
s.Body = s.Body[:0] // preserves capacity
}| Tag | Behavior |
|---|---|
reset:"" |
Zero value |
reset:"value" |
Default value |
reset:"-" |
Skip field |
Specify which structs to generate using the -structs flag:
//go:generate resetgen -structs User,Order,Config
# Or with multiple files
resetgen -structs User,Order,Config ./...When -structs is specified:
- ONLY the listed structs are processed (tags and directives are ignored for struct selection)
- All exported fields are reset to zero values
- Field-level
resettags still work for custom values or to skip specific fields
Example:
//go:generate resetgen -structs User,Order
type User struct {
ID int64
Name string
Secret string `reset:"-"` // Still respected - field will not be reset
}
type Order struct {
ID int64
Items []string
Total float64 `reset:"0.0"` // Custom value still works
}
type Logger struct {
Level string // Will NOT be generated (not in -structs list)
}When you have structs with the same name in different packages, use package-qualified names:
# Process User in models package only
resetgen -structs models.User ./...
# Process User in both models and api packages
resetgen -structs models.User,api.User ./...
# Mix simple and qualified names
resetgen -structs Order,models.User ./...Rules:
- Simple name (
User) → processes ALL User structs in all packages - Qualified name (
models.User) → processes only User in models package - Package path uses Go import path format (lowercase with dots/slashes)
Example with multiple packages:
// models/user.go
//go:generate resetgen -structs models.User,api.User
package models
type User struct {
ID int64 `reset:""`
Name string `reset:""`
Email string `reset:""`
}
// api/user.go
//go:generate resetgen -structs models.User,api.User
package api
type User struct {
ID string `reset:""`
Status string `reset:"active"`
}Both packages can use the same go:generate directive with package-qualified names, and each will generate only its own Reset() method.
Use the +resetgen comment directive to mark structs for automatic Reset() generation without tagging every field:
//go:generate resetgen
package main
// +resetgen
type Request struct {
ID string // defaults to zero value
Method string // defaults to zero value
Headers map[string]string // defaults to zero value
Secret string `reset:"-"` // skipped from reset
}Generated request.gen.go:
func (s *Request) Reset() {
s.ID = ""
s.Method = ""
clear(s.Headers) // preserves capacity
// Secret is not reset (reset:"-")
}- Struct Selection: Structs are processed if they have a
+resetgencomment OR containresettags - Field Processing: All exported fields are reset to zero values
- Custom Values: Fields with explicit
resettags use their specified values - Skip Fields: Use
reset:"-"to exclude specific fields from reset - Unexported Fields: Private fields (lowercase) are automatically skipped for safety
All of these are recognized:
//+resetgen// +resetgen// +resetgen/* +resetgen */
- Allocation-free — slices truncate (
s[:0]), maps clear (clear(m)) - Embedded structs — calls
Reset()recursively - Selective — only structs with
resettags are processed - Fast — single-pass AST, minimal allocations
Tip
Structs without any reset tags are automatically ignored. You can have pooled and regular structs in the same file.
Define a common interface for pooled objects:
type Resetter interface {
Reset()
}All generated Reset() methods satisfy this interface, enabling generic pool helpers.
//go:generate resetgen
package server
type Request struct {
Path string `reset:""`
Method string `reset:"GET"`
Headers map[string]string `reset:""`
Body []byte `reset:""`
UserID int `reset:""`
}
var requestPool = sync.Pool{
New: func() any { return new(Request) },
}
func HandleRequest(path, method string, body []byte) {
req := requestPool.Get().(*Request)
req.Path = path
req.Method = method
req.Body = append(req.Body, body...)
process(req)
req.Reset()
requestPool.Put(req)
}//go:generate resetgen
package pool
type Resetter interface {
Reset()
}
// Pool is a generic, allocation-free pool for any Resetter
type Pool[T Resetter] struct {
p sync.Pool
}
func NewPool[T Resetter](newFn func() T) *Pool[T] {
return &Pool[T]{
p: sync.Pool{New: func() any { return newFn() }},
}
}
func (p *Pool[T]) Get() T { return p.p.Get().(T) }
func (p *Pool[T]) Put(v T) { v.Reset(); p.p.Put(v) }Usage with a buffer:
//go:generate resetgen
package encoding
type Buffer struct {
data []byte `reset:""`
}
var bufPool = pool.NewPool(func() *Buffer { return new(Buffer) })
// MarshalTo writes encoded data directly to dst — zero allocations
func MarshalTo(dst io.Writer, v any) error {
buf := bufPool.Get()
buf.data = encodeJSON(buf.data, v)
_, err := dst.Write(buf.data)
bufPool.Put(buf)
return err
}Note
Both examples avoid defer closures and return values that reference pooled memory.
Detects when sync.Pool.Put() is called without a preceding Reset() call.
go install github.com/flaticols/resetgen/cmd/resetgen-analyzer@latestOr add as a tool dependency (Go 1.24+):
go get -tool github.com/flaticols/resetgen/cmd/resetgen-analyzerRun standalone:
resetgen-analyzer ./...Run with go vet:
go vet -vettool=$(which resetgen-analyzer) ./...Add to your CI pipeline or Makefile:
.PHONY: lint
lint:
go vet -vettool=$(which resetgen-analyzer) ./...func BadUsage() {
buf := bufferPool.Get().(*Buffer)
buf.data = append(buf.data, "hello"...)
bufferPool.Put(buf) // ERROR: sync.Pool.Put() called without Reset() on buf
}
func GoodUsage() {
buf := bufferPool.Get().(*Buffer)
buf.data = append(buf.data, "hello"...)
buf.Reset()
bufferPool.Put(buf) // OK
}| Pattern | Example |
|---|---|
| Global pool | bufferPool.Put(buf) without buf.Reset() |
| Struct field pool | s.pool.Put(buf) without buf.Reset() |
| Wrapped variable | pool.Put(w.buf) without w.buf.Reset() |
| Pool wrapper | p.p.Put(v) without v.Reset() inside wrapper method |
BenchmarkReset-8 1000000000 0.32 ns/op 0 B/op 0 allocs/op
Made with ❤️ by Denis