goreset generates Reset() methods for structs that are reused through a
sync.Pool or a free list: the value goes back to its zero state, but keeps
the memory it owns.
A pooled object has to be cleaned before reuse. *r = T{} is the fast way to
do it, and the wrong one: it drops every slice's backing array, forgets a
reusable *time.Timer, and does not compile for a struct holding a mutex.
A hand-written Reset keeps all of that — until a new field is added and
forgotten there. goreset writes the method from the struct definition, so
it is always complete, and chooses the cheapest form for the type:
// Code generated by goreset; DO NOT EDIT.
// Reset zeroes r in one assignment and gives the slices their arrays back.
func (r *Request) Reset() {
clear(r.Data)
keepData := r.Data[:0]
keepTimer := r.Timer
*r = Request{}
r.Data = keepData
r.Mask = ^uint32(0)
r.Timer = keepTimer
if r.Timer != nil {
r.Timer.Stop()
}
}| Field | Reset |
|---|---|
| slice | clear(s); s = s[:0] — the backing array is kept |
| pointer, map, chan, interface, func | nil |
| numbers, bool, string, arrays, plain structs | zero value |
value with its own Reset() |
r.F.Reset() |
pointer with its own Reset() |
kept; r.F.Reset() when not nil |
atomic.* |
Store(0) / Store(false) / Store(nil) |
sync.* (Mutex, WaitGroup, Pool...) |
left alone |
Struct tags change the treatment of a field:
| Tag | Effect |
|---|---|
reset:"-" |
the field is left as it is |
reset:"value=EXPR" |
set to EXPR instead of zero, e.g. value=^uint32(0) |
reset:"value=pkg.Const,import=example.com/pkg" |
the same with an import the expression needs |
reset:"call=Method" |
kept; r.F.Method() is called (call=Stop for a *time.Timer) |
reset:"release=Func" |
pointer with Reset(): Func(r.F) is called and the field set to nil |
Bulk: the fields to keep are saved, the struct is zeroed in one
assignment, the kept fields are put back. One memclr for the whole struct,
no per-field code for the zeroed ones.
Field by field: one statement per field; sync fields are not touched.
The mode is chosen per type by a cost model, in units of one field store:
| bulk | field by field | |
|---|---|---|
| the struct | memclr: 1 per 16 bytes |
— |
| plain field (number, pointer, string, atomic...) | 0 | 1 |
| slice | 3 (clear, keep, restore) | 2 (clear, reslice) |
value with Reset() |
2 + 2 per 16 bytes (copied out and back) | 1 (the call) |
pointer with Reset(), call=, reset:"-" |
2 (keep, restore) | 1 |
value= |
1 | 1 |
array, struct without Reset() |
0 | 1 per 16 bytes |
The cheaper mode is generated; a sync field forces field-wise mode whatever
the costs. The constants were fitted on two real structs (700 bytes / 95
fields: both modes equal; 2400 bytes / 74 fields: field-wise 2.5x faster) —
a model, not a measurement, so a struct near the break-even point is worth a
benchmark of its own.
The comment of the generated method says which mode and why:
// Reset zeroes r field by field, not in one assignment:
// - zeroing all 2400 bytes and restoring 39 fields costs 314, resetting the 74 fields one by one costs 162and the generator says so on stderr too, with what would change the outcome:
goreset: wrote reqvars_reset.gen.go
goreset: ReqVars is reset field by field; for one assignment:
- ReqVars: one assignment would zero all 2400 bytes and restore 39 fields (cost 314) where resetting the 74 fields one by one costs 162; fewer slices and kept values, or a smaller struct, would turn that around
For a lock the hint is to move it behind a pointer or out of the pooled value.
Package.Analyze
returns the mode, both costs and the hints to other tools.
go get -tool github.com/darxnet/goreset/cmd/goresetRequires Go 1.24 (go get -tool). No dependencies: the package is loaded with
go list -export and go/importer from the standard library.
Put the directive next to the type and run go generate:
//go:generate go tool goreset -type=Request,Response
type Request struct {
Data []byte
Timer *time.Timer `reset:"call=Stop"`
Mask uint32 `reset:"value=^uint32(0)"`
Mu sync.Mutex
}This writes request_reset.gen.go and response_reset.gen.go in the same
package. -dir points the generator at another package.
The generator is also a library — goreset.Load
and Package.Generate —
for tools that want the source without the file.
- It must run without concurrent users of the value: atomics are stored, not swapped, and bulk mode copies the struct.
- Slices keep their capacity. A field that must not keep memory between uses
(a buffer that may grow very large once) should be tagged
reset:"-"and handled by hand, or released throughrelease=. - A nested value's
Reset()is called after the bulk assignment, so it sees its own state, not zeroes.
The sibling modules follow the same rule — nothing allocated on the hot path, the zero value ready to use:
- goshard — sharded, lock-based concurrent map for high-churn data: stores allocate nothing
- gosync — generic concurrent map with
sync.Mapsemantics and no interface boxing, for read-mostly data - gocopy — generates
CopyFrom/Copythat reuse the destination's memory instead of allocating
Apache License 2.0. See LICENSE for details.