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:

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
  }
}
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.