go-functional-options — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-functional-options (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The functional options pattern lets a constructor stay backward compatible while accepting an open-ended set of optional settings. Callers pass only what differs from the defaults; new options never break old call sites.
...Option.fmt.Stringer.| Situation | Pattern |
|---|---|
| 0–2 optional params, stable API | Plain positional or named args |
| Config that callers usually pass whole | Config struct |
| 3+ optional params, growing API | Functional options |
| Mix of "must set together" + "rare overrides" | Config struct + small Option set |
Read references/options-vs-struct.md when choosing between options and a plain config struct, or designing a hybrid.
package db
import "go.uber.org/zap"
// options is the package's private bag of settings.
type options struct {
cache bool
logger *zap.Logger
}
// Option configures Open.
type Option interface {
apply(*options)
}
// --- cacheOption -----------------------------------------------------------
type cacheOption bool
func (c cacheOption) apply(o *options) { o.cache = bool(c) }
// WithCache enables or disables the in-memory cache.
func WithCache(enabled bool) Option { return cacheOption(enabled) }
// --- loggerOption ----------------------------------------------------------
type loggerOption struct{ log *zap.Logger }
func (l loggerOption) apply(o *options) { o.logger = l.log }
// WithLogger sets the logger used by the connection.
func WithLogger(log *zap.Logger) Option { return loggerOption{log: log} }
// --- constructor -----------------------------------------------------------
// Open dials addr using the given options.
func Open(addr string, opts ...Option) (*Connection, error) {
o := options{
cache: true,
logger: zap.NewNop(),
}
for _, opt := range opts {
opt.apply(&o)
}
// ... build the connection from o
return &Connection{}, nil
}db.Open(addr)
db.Open(addr, db.WithLogger(log))
db.Open(addr, db.WithCache(false), db.WithLogger(log))Compare to the alternative where all defaults must be repeated:
db.Open(addr, db.DefaultCache, zap.NewNop()) // tedious// The closure variant — discouraged
type Option func(*options)The interface form wins on:
fmt.Stringer.godoc lists each option type explicitly.Validate()).Closures are shorter to write; they pay for that shortness in introspection.
Set defaults before applying options. A constructor that ignores its defaults is a bug magnet:
o := options{
cache: true,
logger: zap.NewNop(),
}
for _, opt := range opts {
opt.apply(&o)
}If a default needs computation (a temp dir, a process-wide ID), build it once during the constructor — not at package init.
// 1. Unexported settings bag
type options struct { ... }
// 2. Exported interface, unexported method
type Option interface { apply(*options) }
// 3. One option type per setting
type widgetOption Widget
func (w widgetOption) apply(o *options) { o.widget = Widget(w) }
func WithWidget(w Widget) Option { return widgetOption(w) }
// 4. Constructor: defaults, then apply
func New(required string, opts ...Option) (*Thing, error) {
o := options{ /* defaults */ }
for _, opt := range opts { opt.apply(&o) }
return build(required, o)
}| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
Exporting the options struct | External code mutates internals | Keep it unexported |
Option with an exported Apply | Anyone can build an option | Unexported apply method |
| Applying options before defaults | Defaults overwrite caller intent | Defaults first, then apply |
func Option(*options) closures | Opaque in tests/logs | Interface form |
| 7+ positional required params | Caller error-prone | Promote them into a config or options |
Mixing required and optional through ...Option | Required is no longer required | Keep required positional |
options is unexportedOption interface has an unexported apply(*options) methodWith* constructor returning Option...OptionApply or Option func(*options) slipped inWith* and its default~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.