Functional Options — Optimize¶
1. Goal of this file¶
This file is about when the functional-options pattern actually costs you something and when the "fix" is worth shipping. The pattern is cheap in absolute terms — a handful of nanoseconds per option, zero allocations at apply time, one closure allocation per WithX call. For a constructor that runs once per server start, the total cost is invisible.
It becomes visible when:
- The constructor runs per request (HTTP middleware, RPC handlers, per-message decoders).
- The option list is rebuilt on every call instead of reused.
- Closure allocations show up in the heap profile.
- The interface variant's itab lookup adds up across millions of constructions.
The honest envelope: most of the optimizations below save 10–200 ns and 1–6 allocations per construction. That matters at 1M QPS. It does not matter at 100 QPS. Profile first. The middle.md §12 benchmark is the baseline you have to beat:
31.7 ns and zero allocations at apply time. The hidden cost is the five WithX(...) calls before that line, each of which allocates a closure (~16–32 B). Those are where the wins live.
Structure of the file:
- Real wins (§3–§9): closure reuse, sync.Pool, direct init, lazy options, snapshot pattern.
- Wins that aren't (§10–§12): interface vs function, generic options, "compile-time" tricks.
- Cost-benefit framing (§13).
2. Table of Contents¶
- Goal of this file
- Table of Contents
- Exercise 1: Pre-built
[]Optionfor per-request constructors - Exercise 2:
sync.Poolfor transient option slices - Exercise 3: Function vs interface variant at scale
- Exercise 4: Direct field init for internal callers
- Exercise 5: Eliminating
if opt == nilfrom the hot loop - Exercise 6: Composing many options into one closure
- Exercise 7: Avoiding deep copies in nested options
- Exercise 8: Lazy option application
- Exercise 9: Pre-sorting options to skip re-validation
- Exercise 10: The "config snapshot" pattern
- Exercise 11: Code generation for hot-path constructors
- Exercise 12: Avoiding the closure for value-only options
- When NOT to optimize
- The optimization checklist
- Summary
3. Exercise 1: Pre-built []Option for per-request constructors¶
Scenario¶
An HTTP handler builds a per-request tracer.Span with the same five options every time. Each call to WithX(...) allocates a closure on the heap.
Before¶
package tracing
import "time"
type Span struct {
name string
service string
component string
sampleRate float64
timeout time.Duration
}
type Option func(*Span)
func WithService(s string) Option { return func(sp *Span) { sp.service = s } }
func WithComponent(c string) Option { return func(sp *Span) { sp.component = c } }
func WithSampleRate(r float64) Option { return func(sp *Span) { sp.sampleRate = r } }
func WithTimeout(d time.Duration) Option { return func(sp *Span) { sp.timeout = d } }
func NewSpan(name string, opts ...Option) *Span {
s := &Span{name: name, sampleRate: 1.0, timeout: 30 * time.Second}
for _, o := range opts {
o(s)
}
return s
}
// Handler called ~50_000 times per second
func handleRequest(name string) *Span {
return NewSpan(name,
WithService("checkout"),
WithComponent("api"),
WithSampleRate(0.01),
WithTimeout(5*time.Second),
)
}
Benchmark¶
func BenchmarkBefore(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = handleRequest("checkout.pay")
}
}
Four closure allocations (one per WithX) plus one *Span. At 50k QPS that's 200k closure allocs/sec just from option construction.
After
// Pre-build the option list once at package init.
var defaultSpanOpts = []Option{
WithService("checkout"),
WithComponent("api"),
WithSampleRate(0.01),
WithTimeout(5 * time.Second),
}
func handleRequest(name string) *Span {
return NewSpan(name, defaultSpanOpts...)
}
var baseSpanOpts = []Option{
WithComponent("api"),
WithSampleRate(0.01),
WithTimeout(5 * time.Second),
}
func handleRequest(name, service string) *Span {
return NewSpan(name, append(baseSpanOpts, WithService(service))...)
}
4. Exercise 2: sync.Pool for transient option slices¶
Scenario¶
A logging library builds a one-off []Option slice per log call to capture context. The slice itself is a heap allocation. Calls happen millions of times per second.
Before¶
package logger
type Entry struct {
level int
fields map[string]any
}
type Option func(*Entry)
func WithField(k string, v any) Option {
return func(e *Entry) {
if e.fields == nil { e.fields = make(map[string]any) }
e.fields[k] = v
}
}
func NewEntry(level int, opts ...Option) *Entry {
e := &Entry{level: level}
for _, o := range opts {
o(e)
}
return e
}
func Log(level int, msg string, ctx map[string]any) {
opts := make([]Option, 0, len(ctx))
for k, v := range ctx {
opts = append(opts, WithField(k, v))
}
_ = NewEntry(level, opts...)
}
Benchmark¶
func BenchmarkLogBefore(b *testing.B) {
ctx := map[string]any{"user": "u1", "trace": "t1", "req": 42}
for i := 0; i < b.N; i++ {
Log(1, "hello", ctx)
}
}
One []Option slice + three WithField closures + one *Entry + the map + a couple of any boxes. The opts slice is the biggest single allocation.
After
var optPool = sync.Pool{
New: func() any {
s := make([]Option, 0, 8)
return &s
},
}
func Log(level int, msg string, ctx map[string]any) {
optsPtr := optPool.Get().(*[]Option)
opts := (*optsPtr)[:0]
for k, v := range ctx {
opts = append(opts, WithField(k, v))
}
_ = NewEntry(level, opts...)
*optsPtr = opts[:0]
optPool.Put(optsPtr)
}
5. Exercise 3: Function vs interface variant at scale¶
Scenario¶
You're choosing between type Option func(*T) and type Option interface { apply(*T) } for a constructor that runs 1M times/sec. middle.md said "the interface variant is ~30% slower per option". Let's measure honestly and see whether that translates to anything you'd actually notice.
Before (interface variant)¶
package config
type Config struct {
a, b, c, d, e int
}
type Option interface{ apply(*Config) }
type aOpt struct{ v int }
func (o aOpt) apply(c *Config) { c.a = o.v }
type bOpt struct{ v int }
func (o bOpt) apply(c *Config) { c.b = o.v }
// ... cOpt, dOpt, eOpt similar
func WithA(v int) Option { return aOpt{v} }
func WithB(v int) Option { return bOpt{v} }
func WithC(v int) Option { return cOpt{v} }
func WithD(v int) Option { return dOpt{v} }
func WithE(v int) Option { return eOpt{v} }
func New(opts ...Option) *Config {
c := &Config{}
for _, o := range opts {
o.apply(c)
}
return c
}
Benchmark¶
func BenchmarkIface(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = New(WithA(1), WithB(2), WithC(3), WithD(4), WithE(5))
}
}
After (function variant)
12 ns saved per construction. At 1M QPS that's 12 ms/sec of CPU — barely measurable on a multi-core box. **Why the interface variant is slower.** Each `o.apply(c)` call goes through an itab (interface table) lookup: the runtime resolves "what concrete `apply` method does *this* `Option` value point at?". The function variant calls a function value directly — one indirect jump, no table lookup. **Why this rarely matters.** The interface variant's advantages (external extensibility, private options, multiple methods per option) are concrete *design* wins. The function variant's advantage is 12 ns. Choose by design needs, not by this benchmark. **Trade-off summary.** | Variant | Per-call cost | Extensibility | Multiple methods | Private options | |---------|---------------|---------------|------------------|-----------------| | Function | ~6 ns / option | Hard (would need to expose the named type) | No | Awkward (only via package-private wrapper) | | Interface | ~9 ns / option | External packages can implement `Option` | Yes (`String()`, `equals()`) | Easy (unexported impl type) | **When NOT to optimize this.** If your constructor runs < 100k times/sec, the difference is invisible. gRPC chose the interface variant for a 100M+ QPS codebase because *extensibility* mattered, not the 3 ns. **pprof:** You'll see `runtime.assertI2I` or `runtime.convI` in the interface-variant profile — those are the itab calls. The function variant has none.6. Exercise 4: Direct field init for internal callers¶
Scenario¶
A library exposes NewServer(opts ...Option) to users and uses it internally for default instances. Internal users always pass the same five options. The WithX ceremony costs the closure allocations on every internal call, for no API benefit (the caller is in the same package).
Before¶
func defaultServer() *Server {
return NewServer(":8080",
WithReadTimeout(30*time.Second),
WithWriteTimeout(30*time.Second),
WithMaxConns(1000),
WithLogger(log.Default()),
WithBufferSize(4096),
)
}
Benchmark¶
Five closure allocations + the *Server.
After
// internal_new.go (same package)
// Direct construction. Used only from within the package.
func newServerDirect() *Server {
return &Server{
addr: ":8080",
readTimeout: 30 * time.Second,
writeTimeout: 30 * time.Second,
maxConns: 1000,
logger: log.Default(),
bufferSize: 4096,
}
}
func defaultServer() *Server {
return newServerDirect()
}
func defaults() *Server {
return &Server{
readTimeout: 30 * time.Second,
writeTimeout: 30 * time.Second,
maxConns: 1000,
logger: log.Default(),
bufferSize: 4096,
}
}
func NewServer(addr string, opts ...Option) *Server {
s := defaults()
s.addr = addr
for _, o := range opts { o(s) }
return s
}
func newServerDirect() *Server {
s := defaults()
s.addr = ":8080"
return s
}
7. Exercise 5: Eliminating if opt == nil from the hot loop¶
Scenario¶
The constructor accepts options from external sources (config file, plugin registry) where nil entries are possible. middle.md §15.3 suggested if opt == nil { continue }. That nil-check executes once per option per call. For 50 options × 1M constructions/sec, that's 50M branch instructions/sec.
Before¶
func NewServer(addr string, opts ...Option) *Server {
s := &Server{addr: addr}
for _, opt := range opts {
if opt == nil {
continue
}
opt(s)
}
return s
}
Benchmark¶
var nilHeavy = []Option{
WithA(1), nil, WithB(2), nil, WithC(3), nil, WithD(4), nil, WithE(5),
}
func BenchmarkNilHeavy(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewServer(":8080", nilHeavy...)
}
}
After
// Filter once at the source.
func filterNils(opts []Option) []Option {
out := opts[:0]
for _, o := range opts {
if o != nil {
out = append(out, o)
}
}
return out
}
// At config load time, not per-construction
var validOpts = filterNils(loadOptionsFromConfig())
func NewServer(addr string, opts ...Option) *Server {
s := &Server{addr: addr}
for _, opt := range opts {
opt(s) // no nil check
}
return s
}
// hot path
func handler() *Server {
return NewSenseValidOpts(validOpts)
}
8. Exercise 6: Composing many options into one closure¶
Scenario¶
A service applies 50 options per construction. The loop overhead (range, function call, parameter passing) is a measurable fraction of total time. Composing them into a single closure reduces 50 function calls to 1.
Before¶
var fiftyOpts = make([]Option, 50)
func init() {
for i := range fiftyOpts {
v := i
fiftyOpts[i] = func(s *Server) { s.values[v] = v }
}
}
func BenchmarkFiftyOpts(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewServer(":8080", fiftyOpts...)
}
}
The 480 ns is mostly the loop: 50 indirect function calls.
After
// Compose 50 options into a single closure once.
func Compose(opts ...Option) Option {
return func(s *Server) {
for _, o := range opts {
o(s)
}
}
}
var composedFifty = Compose(fiftyOpts...)
func BenchmarkComposed(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewServer(":8080", composedFifty)
}
}
// Compose lets the compiler inline the inner loop in some cases.
// More importantly, it reduces the *variadic slice* to a single Option,
// which avoids the slice allocation if you previously had to build one.
func handler(extra Option) *Server {
return NewServer(":8080", composedFifty, extra)
// vs:
// opts := append(fiftyOpts, extra) // allocation
// return NewServer(":8080", opts...)
}
9. Exercise 7: Avoiding deep copies in nested options¶
Scenario¶
A Server contains an *http.Server and a *tls.Config. Some options modify the embedded structs. If the option performs a defensive deep copy on every call, you pay for the copy on every construction.
Before¶
type Server struct {
httpSrv *http.Server
tls *tls.Config
}
func WithTLS(cfg *tls.Config) Option {
return func(s *Server) {
// Defensive deep copy
s.tls = cfg.Clone()
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
httpSrv: &http.Server{Addr: addr},
tls: &tls.Config{},
}
for _, o := range opts { o(s) }
return s
}
Benchmark¶
var sharedTLS = &tls.Config{MinVersion: tls.VersionTLS12}
func BenchmarkWithTLS(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewServer(":8080", WithTLS(sharedTLS))
}
}
tls.Config.Clone() is expensive: it copies internal slice/map fields.
After
// Document that the caller must not mutate cfg after passing it.
func WithTLS(cfg *tls.Config) Option {
return func(s *Server) {
s.tls = cfg // share the pointer
}
}
10. Exercise 8: Lazy option application¶
Scenario¶
Some options are expensive to apply — they load files, build state, or initialize subsystems. The constructor applies them eagerly even if the server never uses them.
Before¶
type Server struct {
metrics *metrics.Collector // built only if WithMetrics() was called
}
func WithMetrics(addr string) Option {
return func(s *Server) {
// Connects to Prometheus pushgateway, blocks ~50ms
s.metrics = metrics.New(addr)
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{}
for _, o := range opts { o(s) }
return s
}
Benchmark¶
func BenchmarkWithMetrics(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewServer(":8080", WithMetrics("pushgateway:9091"))
}
}
52 ms per construction, dominated by the metrics initialization.
After
type Server struct {
metricsAddr string
metricsOnce sync.Once
metrics *metrics.Collector
}
// Option stores the address only.
func WithMetrics(addr string) Option {
return func(s *Server) { s.metricsAddr = addr }
}
// Lazily initialize when first used.
func (s *Server) Metrics() *metrics.Collector {
s.metricsOnce.Do(func() {
if s.metricsAddr != "" {
s.metrics = metrics.New(s.metricsAddr)
}
})
return s.metrics
}
// Eager for required subsystems, lazy for optional ones.
func NewServer(addr string, opts ...Option) (*Server, error) {
s := &Server{}
for _, o := range opts { o(s) }
// Eagerly init *required* subsystems
if err := s.connectDB(); err != nil { return nil, err }
// Defer optional ones
return s, nil
}
11. Exercise 9: Pre-sorting options to skip re-validation¶
Scenario¶
The constructor enforces cross-option dependencies: WithTLS requires WithCert first, WithMetrics requires WithLogger, etc. Naive enforcement re-checks every dependency on every construction. If you can sort the option list once, you can drop the checks.
Before¶
func NewServer(addr string, opts ...Option) (*Server, error) {
s := &Server{addr: addr}
for _, o := range opts { o(s) }
// Validate dependencies (runs on every call)
if s.tlsCert == nil && s.tlsKey != nil {
return nil, errors.New("WithKey requires WithCert")
}
if s.metricsEnabled && s.logger == nil {
return nil, errors.New("WithMetrics requires WithLogger")
}
if s.tracingEnabled && s.metricsEnabled == false {
return nil, errors.New("WithTracing requires WithMetrics")
}
// ...10 more checks
return s, nil
}
Benchmark¶
func BenchmarkValidating(b *testing.B) {
opts := []Option{WithCert(c), WithKey(k), WithLogger(l), WithMetrics(m), WithTracing(t)}
for i := 0; i < b.N; i++ {
_, _ = NewServer(":8080", opts...)
}
}
The 10 validation checks add ~40 ns per call.
After
// Validate the option set ONCE, off the hot path.
type ValidatedOpts struct {
opts []Option
}
func Validate(opts ...Option) (*ValidatedOpts, error) {
// Run a probe construction to detect dependency errors.
probe := &Server{}
for _, o := range opts { o(probe) }
if probe.tlsCert == nil && probe.tlsKey != nil {
return nil, errors.New("WithKey requires WithCert")
}
// ...rest of checks
return &ValidatedOpts{opts: opts}, nil
}
// Hot-path constructor skips validation.
func NewServerValidated(addr string, v *ValidatedOpts) *Server {
s := &Server{addr: addr}
for _, o := range v.opts { o(s) }
return s
}
// Public, validates every time
func NewServer(addr string, opts ...Option) (*Server, error) { ... }
// Internal, skip validation. Use only with opts from a trusted source.
func newServerUnchecked(addr string, opts []Option) *Server {
s := &Server{addr: addr}
for _, o := range opts { o(s) }
return s
}
12. Exercise 10: The "config snapshot" pattern¶
Scenario¶
You build 1M servers from the same option set. Each construction repeats the same work: apply 10 options, set 10 fields. The result is identical every time. Precompute the result and copy.
Before¶
func NewServer(addr string, opts ...Option) *Server {
s := &Server{addr: addr, /* defaults */}
for _, o := range opts { o(s) }
return s
}
func handler(addr string) *Server {
return NewServer(addr, fiveStdOpts...)
}
Benchmark¶
After
// Pre-compute the configured server once.
var snapshot = func() Server {
s := &Server{}
for _, o := range fiveStdOpts { o(s) }
return *s // dereference to a value
}()
func handler(addr string) *Server {
s := snapshot // struct copy, on the stack
s.addr = addr
return &s
}
// BUG: every server shares the same logger
var snapshot = func() Server {
s := &Server{logger: log.New(...)}
return *s
}()
func handler(addr string) *Server {
s := snapshot
s.logger.SetPrefix("server1:") // mutates snapshot.logger too!
return &s
}
type Server struct {
addr string // value
timeout time.Duration // value
bufferSize int // value
logger *log.Logger // pointer — DO NOT snapshot
}
var configSnapshot = struct {
timeout time.Duration
bufferSize int
}{
timeout: 30 * time.Second,
bufferSize: 4096,
}
func handler(addr string, logger *log.Logger) *Server {
return &Server{
addr: addr,
timeout: configSnapshot.timeout,
bufferSize: configSnapshot.bufferSize,
logger: logger,
}
}
13. Exercise 11: Code generation for hot-path constructors¶
Scenario¶
You have 30 options. The generic constructor's option loop is unavoidable. But for the 5 most-common combinations, you can generate specialized constructors at build time that inline every field assignment.
Before¶
// Generic constructor used everywhere
func NewServer(addr string, opts ...Option) *Server { ... }
// Hot path: most calls use this exact combination
func handler(addr string) *Server {
return NewServer(addr,
WithReadTimeout(5*time.Second),
WithWriteTimeout(10*time.Second),
WithMaxConns(1000),
)
}
After
A `go generate` directive produces specialized constructors://go:generate go run gen.go
// Specialized constructor — generated.
// Equivalent to NewServer(addr, WithReadTimeout(5s), WithWriteTimeout(10s), WithMaxConns(1000))
func NewServerStdAPI(addr string) *Server {
return &Server{
addr: addr,
readTimeout: 5 * time.Second,
writeTimeout: 10 * time.Second,
maxConns: 1000,
logger: defaultLogger,
bufferSize: defaultBufferSize,
}
}
func handler(addr string) *Server {
return NewServerStdAPI(addr)
}
// gen.go (run with go generate)
package main
import (
"os"
"text/template"
)
type spec struct {
Name string
Addr string
Read string
Write string
Max int
}
const tmpl = `// Code generated. DO NOT EDIT.
package server
import "time"
func New{{.Name}}(addr string) *Server {
return &Server{
addr: addr,
readTimeout: {{.Read}},
writeTimeout: {{.Write}},
maxConns: {{.Max}},
logger: defaultLogger,
bufferSize: defaultBufferSize,
}
}
`
func main() {
specs := []spec{
{Name: "ServerStdAPI", Read: "5*time.Second", Write: "10*time.Second", Max: 1000},
{Name: "ServerInternalRPC", Read: "1*time.Second", Write: "2*time.Second", Max: 100},
}
t := template.Must(template.New("c").Parse(tmpl))
f, _ := os.Create("generated_constructors.go")
defer f.Close()
for _, s := range specs { t.Execute(f, s) }
}
14. Exercise 12: Avoiding the closure for value-only options¶
Scenario¶
Most options just set one field to one value: func(s *Server) { s.X = v }. Each WithX(v) allocates a closure that captures v. For options where v is a small value (int, bool, duration), the closure is pure overhead.
Before¶
Each call to WithTimeout allocates 16–24 bytes for the closure (the captured d plus closure header).
Benchmark¶
func BenchmarkClosure(b *testing.B) {
for i := 0; i < b.N; i++ {
opt := WithTimeout(5 * time.Second)
_ = opt
}
}
After
Use the interface variant with a struct that holds the value directly. No closure, no capture, no allocation if the struct is small enough to fit in the interface's "small object" slot.type Option interface{ apply(*Server) }
type timeoutOpt time.Duration
func (o timeoutOpt) apply(s *Server) { s.timeout = time.Duration(o) }
func WithTimeout(d time.Duration) Option { return timeoutOpt(d) }
15. When NOT to optimize¶
The honest framing for this entire file: most of these optimizations are not worth it. The pattern's defaults are good. The wins exist only when:
| Condition | Threshold to bother |
|---|---|
| Constructor frequency | > 100k calls/sec sustained |
| Profile shows option construction in top 5 % | Yes |
| Allocation profile shows option closures in top 10 | Yes |
| The "fix" doesn't break encapsulation in nasty ways | Yes |
| You can write a regression test | Yes |
| The fix survives an upgrade of Go major version | Probably yes |
If you can't tick most of those boxes, don't optimize. The clean-API version of functional options is shipping in grpc-go, zap, chi, and the entire Go standard library. None of them have hit a performance wall from the pattern itself.
Specific anti-patterns to avoid:
| Anti-pattern | Why it's bad |
|---|---|
| Replacing functional options with a config struct "for speed" | Loses the API benefits (defaults, non-breaking additions) for sub-nanosecond gains |
| Skipping the nil-check "because options are never nil" | True until the day a teammate adds a conditional nil and you spend an afternoon debugging the panic |
| Pre-building options for every code path | Memory cost adds up; only do it for hot paths |
| Code generation for < 5× speedup | Build complexity isn't free; reserve for genuine bottlenecks |
| Snapshot pattern with pointer fields | Subtle aliasing bugs that survive code review |
The default answer to "can we make functional options faster here?" is no, the pattern is fine. The yes cases are narrow and benchmark-justified.
16. The optimization checklist¶
Before shipping any of the above:
- Baseline benchmark exists (the unoptimized version).
- Optimized benchmark shows ≥ 2× improvement or saves ≥ 1 allocation per call.
-
pprofconfirms the optimization targets a real hot spot (top 5 % CPU or top 10 allocs). - The new code passes the same tests as the old.
-
-gcflags=-mshows no unexpected new escapes. -
-raceis clean (especially for the snapshot/pointer-sharing patterns). - Documentation explains the assumption the optimization makes (e.g., "do not mutate
cfgafter passing it"). - CI regression test (
benchstat) compares against the baseline. - Code review has signed off on the trade-off (someone else looked at it and agreed the win is worth the new constraint).
- The "When NOT to do this" condition from the relevant exercise has been checked.
If any item is missing, the optimization isn't ready.
17. Summary¶
The functional-options pattern is already fast: ~30 ns per construction with 5 options, zero allocations at apply time. The hidden cost is the per-WithX closure allocation, which matters only when the same option list is built repeatedly at high frequency.
The wins worth shipping cluster in five areas:
- Pre-build the option slice (Exercise 1) — eliminate per-call closure allocations when the option set is fixed.
sync.Poolfor transient slices (Exercise 2) — recycle the slice header itself when options vary per call.- Direct field init for internal callers (Exercise 4) — skip the pattern entirely when the API benefit doesn't apply.
- Lazy option application (Exercise 8) — defer expensive option work until first use.
- Snapshot pattern for fixed configs (Exercise 10) — precompute the result and copy.
The wins that don't pay off:
- Composing options into a single closure (Exercise 6) — the work is the same, just relocated.
- Function vs interface variant for raw speed (Exercise 3) — 3 ns difference per option, dominated by design considerations.
- Code generation for moderate speedups (Exercise 11) — build complexity exceeds the benefit for < 10× wins.
Always benchmark. Always check escape analysis. Always confirm the optimization survives a Go version bump (the snapshot test in CI is your friend). Most production codebases need none of these optimizations; the pattern is fine as written in junior.md.
Further reading¶
sync.Pool: https://pkg.go.dev/sync#Pool- Escape analysis: https://github.com/golang/go/wiki/CompilerOptimizations
benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat- Sibling: middle.md — variant choices
- Related: 02-builder-pattern — when functional options are the wrong tool
- Related: closure-internals — what a closure actually allocates
- Inspiration (zero-allocation option patterns): https://github.com/uber-go/zap/blob/master/options.go