About
This post looks at the functional options pattern and how it can help you write Go programs that can initialize structs in idiomatic Go.
Background
Context
I believe the first time I came across the pattern we will discuss was with goldmark (a nice Go markdown library). At first, the pattern was a mystery that I took note of but had no idea how it worked. Eventually I started to build my own libraries, and I wanted them to be idiomatic Go libraries. That's when I started to research what is known as the functional options pattern.
Pattern
The functional options pattern implements a technique that can initialize and configure Go structs with a syntax that is readable and expressive.
The pattern typically revolves around a New function, and
it usually belongs to a package other than main. The New
function accepts a variable number of Option arguments,
where Option is a function type defined to receive a pointer to the
struct being configured (eg type Option func(*User)).
The Option values are created by option
functions (like SetName or SetAge)
that are exported by a package. Each of these functions takes a desired
configuration value, then returns a new Option function. This returned
Option function "closes over" the provided value, and when the New
function iterates and calls it, the function will set a property on the
struct.
The next step is to illustrate the explanation with an experiment that demonstrates how the pattern works in practice.
Experiment
Context
The following experiment illustrates the functional options pattern
described in the previous step by defining a User struct and
a collection of public package functions that configure the User
struct:
- 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
The user package defines a User struct and an Option type for
configuring the struct. The New constructor initializes a User with
default values, then applies any provided Option functions, such as
SetName and SetAge.
Conclusion
The functional options pattern can help Go programmers design idiomatic libraries that library consumers will be happy to use.