About
This post walks through the functional options pattern and why I like it for building Go APIs that stay tidy as they grow.
Background
Overview
I first ran into this pattern while using the goldmark library (a great Go markdown library). At the time it felt a little mysterious, so I filed it away and moved on. Later, when I started writing my own libraries, I wanted the APIs to feel idiomatic, so I circled back and learned how the functional options pattern works.
Pattern
The functional options pattern is a simple way to configure a struct without stuffing a constructor with lots of parameters.
Instead of passing a long list of values, you pass option functions to
New. Each option function receives a pointer to the struct
and sets one field.
Option functions are usually small helpers like SetName
or SetAge. They capture a value, return a function, and
New runs them to apply those values.
Experiment
Overview
The example below defines a User struct and a few package
functions that configure it:
- user/user.go
package user
type User struct {
name string
age uint8
}
type Option func(*User)
func New(opts ...Option) *User {
u := &User{name: "unknown", age: uint8(0)}
for _, set := range opts {
set(u)
}
return u
}
func SetName(name string) Option {
return func(u *User) {
u.name = name
}
}
func SetAge(age uint8) Option {
return func (u *User) {
u.age = age
}
}
- cmd/main/main.go
package main
import (
"user"
)
func main() {
u := user.New(
user.SetName("Mario"),
user.SetAge(uint8(37))
)
}
Explanation
Useris the struct we want to configure.SetNameandSetAgeeach capture a value and return an option function.- Each option function sets one field on
User. Newbuilds a defaultUser, then runs all the options to apply those values.- In
main, passingSetName(...)andSetAge(...)configures the struct in one line.