From golang-skills
Guides implementing Go's functional options pattern for constructors and factories with 3+ optional parameters or extensible APIs, ensuring clean caller experience.
How this skill is triggered — by the user, by Claude, or both
Slash command
/golang-skills:go-functional-optionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Functional options is a pattern where you declare an opaque `Option` type that records information in an internal struct. The constructor accepts a variadic number of these options and applies them to configure the result.
Functional options is a pattern where you declare an opaque Option type that records information in an internal struct. The constructor accepts a variadic number of these options and applies them to configure the result.
Use functional options when:
options struct - holds all configurationOption interface - with unexported apply methodWith* constructors - create optionstype Option interface {
apply(*options)
}
The unexported apply method ensures only options from this package can be used.
package db
import "go.uber.org/zap"
// options holds all configuration for opening a connection.
type options struct {
cache bool
logger *zap.Logger
}
// Option configures how we open the connection.
type Option interface {
apply(*options)
}
// cacheOption implements Option for cache setting (simple type alias).
type cacheOption bool
func (c cacheOption) apply(opts *options) {
opts.cache = bool(c)
}
// WithCache enables or disables caching.
func WithCache(c bool) Option {
return cacheOption(c)
}
// loggerOption implements Option for logger setting (struct for pointers).
type loggerOption struct {
Log *zap.Logger
}
func (l loggerOption) apply(opts *options) {
opts.logger = l.Log
}
// WithLogger sets the logger for the connection.
func WithLogger(log *zap.Logger) Option {
return loggerOption{Log: log}
}
// Open creates a connection.
func Open(addr string, opts ...Option) (*Connection, error) {
// Start with defaults
options := options{
cache: defaultCache,
logger: zap.NewNop(),
}
// Apply all provided options
for _, o := range opts {
o.apply(&options)
}
// Use options.cache and options.logger...
return &Connection{}, nil
}
// Caller must always provide all parameters, even defaults
db.Open(addr, db.DefaultCache, zap.NewNop())
db.Open(addr, db.DefaultCache, log)
db.Open(addr, false /* cache */, zap.NewNop())
db.Open(addr, false /* cache */, log)
// Only provide options when needed
db.Open(addr)
db.Open(addr, db.WithLogger(log))
db.Open(addr, db.WithCache(false))
db.Open(
addr,
db.WithCache(false),
db.WithLogger(log),
)
| Aspect | Functional Options | Config Struct |
|---|---|---|
| Extensibility | Add new With* functions | Add new fields (may break) |
| Defaults | Built into constructor | Zero values or separate defaults |
| Caller experience | Only specify what differs | Must construct entire struct |
| Testability | Options are comparable | Struct comparison |
| Complexity | More boilerplate | Simpler setup |
Prefer Config Struct when: Fewer than 3 options, options rarely change, all options usually specified together, or internal APIs only.
Read references/OPTIONS-VS-STRUCTS.md when deciding between functional options and config structs, designing a config struct API with proper defaults, or evaluating the hybrid approach for complex constructors.
An alternative implementation uses closures:
// Closure approach (not recommended)
type Option func(*options)
func WithCache(c bool) Option {
return func(o *options) { o.cache = c }
}
The interface approach is preferred because:
fmt.Stringer// 1. Unexported options struct with defaults
type options struct {
field1 Type1
field2 Type2
}
// 2. Exported Option interface, unexported method
type Option interface {
apply(*options)
}
// 3. Option type + apply + With* constructor
type field1Option Type1
func (o field1Option) apply(opts *options) { opts.field1 = Type1(o) }
func WithField1(v Type1) Option { return field1Option(v) }
// 4. Constructor applies options over defaults
func New(required string, opts ...Option) (*Thing, error) {
o := options{field1: defaultField1, field2: defaultField2}
for _, opt := range opts {
opt.apply(&o)
}
// ...
}
options struct is unexportedOption interface has unexported apply methodWith* constructor...OptionOption interface or choosing between interface and closure approachesWith* constructors, option types, or the unexported options structOption types, With* functions, or constructor behaviornpx claudepluginhub cxuu/golang-skills --plugin golang-skillsDocuments idiomatic Go design patterns — functional options, constructors, error handling, graceful shutdown, resilience — applicable when choosing architectural patterns or implementing Go APIs.
Provides idiomatic Go patterns for simple, maintainable code: zero values, accept interfaces return structs, contextual error wrapping, custom errors. Useful when writing, reviewing, or refactoring Go.
Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications.