https://github.com/flaticols/resetgen
Go code generator for allocation-free Reset() methods, optimized for sync.Pool usage
https://github.com/flaticols/resetgen
code-generator go go-generate golang performance sync-pool zero-allocation
Last synced: 3 days ago
JSON representation
Go code generator for allocation-free Reset() methods, optimized for sync.Pool usage
- Host: GitHub
- URL: https://github.com/flaticols/resetgen
- Owner: flaticols
- License: mit
- Created: 2025-12-14T11:46:45.000Z (4 months ago)
- Default Branch: latest
- Last Pushed: 2025-12-15T22:56:16.000Z (4 months ago)
- Last Synced: 2025-12-19T07:11:26.311Z (4 months ago)
- Topics: code-generator, go, go-generate, golang, performance, sync-pool, zero-allocation
- Language: Go
- Homepage:
- Size: 45.9 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# resetgen
Generate allocation-free `Reset()` methods for your structs. Perfect for [`sync.Pool`](https://pkg.go.dev/sync) usage.
## Tools
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()` |
---
## resetgen (Code Generator)
### Installation
```bash
go install github.com/flaticols/resetgen@latest
```
Or add as a tool dependency (Go 1.24+):
```bash
go get -tool github.com/flaticols/resetgen@latest
```
### Go 1.24+ Tool Mechanism
Go 1.24 introduced the ability to manage CLI tools as dependencies. You can declare tool requirements in `go.mod`:
```go
tool (
github.com/flaticols/resetgen
)
```
Run with `go tool`:
```bash
# 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 -version
```
This approach keeps your tool versions synchronized with your project, just like regular dependencies.
### Usage
Add `reset` tags to your struct fields and run the generator:
```go
//go:generate resetgen
package main
type Request struct {
ID string `reset:""`
Method string `reset:"GET"`
Headers map[string]string `reset:""`
Body []byte `reset:""`
}
```
Run:
```bash
go generate ./...
```
Generated `request.gen.go`:
```go
func (s *Request) Reset() {
s.ID = ""
s.Method = "GET"
clear(s.Headers) // preserves capacity
s.Body = s.Body[:0] // preserves capacity
}
```
## Tag Syntax
| Tag | Behavior |
|-----|----------|
| `reset:""` | Zero value |
| `reset:"value"` | Default value |
| `reset:"-"` | Skip field |
## CLI Flag Syntax
### `-structs` Flag
Specify which structs to generate using the `-structs` flag:
```bash
//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 `reset` tags still work for custom values or to skip specific fields
**Example:**
```go
//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)
}
```
### Package-Qualified Names
When you have structs with the same name in different packages, use package-qualified names:
```bash
# 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:**
```go
// 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.
## Directive Syntax
Use the `+resetgen` comment directive to mark structs for automatic `Reset()` generation without tagging every field:
```go
//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`:
```go
func (s *Request) Reset() {
s.ID = ""
s.Method = ""
clear(s.Headers) // preserves capacity
// Secret is not reset (reset:"-")
}
```
### How Directive Works
- **Struct Selection**: Structs are processed if they have a `+resetgen` comment OR contain `reset` tags
- **Field Processing**: All exported fields are reset to zero values
- **Custom Values**: Fields with explicit `reset` tags use their specified values
- **Skip Fields**: Use `reset:"-"` to exclude specific fields from reset
- **Unexported Fields**: Private fields (lowercase) are automatically skipped for safety
### Directive Formats
All of these are recognized:
- `//+resetgen`
- `// +resetgen`
- `// +resetgen`
- `/* +resetgen */`
## Features
- **Allocation-free** — slices truncate (`s[:0]`), maps clear (`clear(m)`)
- **Embedded structs** — calls `Reset()` recursively
- **Selective** — only structs with `reset` tags 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.
## Resetter Interface
Define a common interface for pooled objects:
```go
type Resetter interface {
Reset()
}
```
All generated `Reset()` methods satisfy this interface, enabling generic pool helpers.
## sync.Pool Examples
### Example 1: HTTP Request Pool
```go
//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)
}
```
### Example 2: Generic Pool with Resetter
```go
//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
//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.
---
## resetgen-analyzer (Static Analyzer)
Detects when `sync.Pool.Put()` is called without a preceding `Reset()` call.
### Installation
```bash
go install github.com/flaticols/resetgen/cmd/resetgen-analyzer@latest
```
Or add as a tool dependency (Go 1.24+):
```bash
go get -tool github.com/flaticols/resetgen/cmd/resetgen-analyzer
```
### Usage
Run standalone:
```bash
resetgen-analyzer ./...
```
Run with `go vet`:
```bash
go vet -vettool=$(which resetgen-analyzer) ./...
```
Add to your CI pipeline or Makefile:
```makefile
.PHONY: lint
lint:
go vet -vettool=$(which resetgen-analyzer) ./...
```
### What it detects
```go
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
}
```
### Detected patterns
| 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 |
## Benchmarks
```
BenchmarkReset-8 1000000000 0.32 ns/op 0 B/op 0 allocs/op
```
## License
[MIT](LICENSE)
---
Made with ❤️ by Denis