go-optional

go-optional is the gomatic ecosystem’s generic implementation of the functional-options pattern for Go. It provides a single generic interface — Option[T] — that any value can satisfy by knowing how to Apply(*T) to a target, plus the constructors (New, NewWithDefault) that compose those options into a finished T. Despite its name, it is not a Some/None/Maybe/Option monad: it is the composable-configuration pattern for building friendly constructor APIs, not a nullable/optional value type.

The import path is github.com/gomatic/go-optional; the package name is optional. Requires Go 1.26+.

Install

go get github.com/gomatic/go-optional

Functional options, not a Maybe monad

The functional-options pattern lets a constructor take a variadic list of composable, self-describing configuration values instead of a long positional argument list or a mutable config struct. Each option knows how to mutate the value being built; the constructor applies them in order, last-write-wins. The result is a friendly, extensible API where callers pass only the options they care about and defaults fill in the rest.

go-optional owns the mechanism only — it ships no concrete options. An Option[T] is anything that implements Apply(*T); every consumer declares its own option types for its own struct:

import optional "github.com/gomatic/go-optional"

type Thing struct{ name string }

// Name is a functional option for Thing — a value that configures a *Thing.
type Name string

func (n Name) Apply(t *Thing) { optional.Must(t).name = string(n) }

Usage

Build a value from options

New starts from the zero value of T and applies each option on top; NewWithDefault starts from a default T you provide instead.

package main

import (
	"fmt"

	optional "github.com/gomatic/go-optional"
)

type Thing struct{ name string }

type Name string

func (n Name) Apply(t *Thing) { optional.Must(t).name = string(n) }

func main() {
	thing := optional.New[Thing](Name("example"))
	withDefault := optional.NewWithDefault(Thing{name: "default"}, Name("override"))

	fmt.Printf("%+v\n", thing)       // {name:example}
	fmt.Printf("%+v\n", withDefault) // {name:override}
}

A nil option in the slice is skipped, and options are applied left-to-right so later options win over earlier ones and over the default.

Define options as var or const

Because an option is just a typed value, it can be declared either way and passed straight to New:

varName := Name("a var Name")
const constName = Name("a const Name")

optional.New[Thing](varName)   // {name:a var Name}
optional.New[Thing](constName) // {name:a const Name}

Wrap the constructor (optional)

When a constructor does nothing but apply options over a default, you do not strictly need it — callers can reach for New or NewWithDefault directly. A thin wrapper is still convenient for baking in package defaults:

func NewThing(opts ...optional.Option[Thing]) Thing {
	return optional.NewWithDefault(Thing{name: "simplifies defaults"}, opts...)
}

NewThing()                  // {name:simplifies defaults}
NewThing(Name("like first")) // {name:like first}

Must — guard against a nil receiver

Must returns a non-nil *T: hand it a nil and you get a fresh *T back, otherwise you get the pointer untouched. Using it at the top of an Apply method makes the option a no-op against a nil target instead of panicking. If you would rather have the panic, skip Must and let the usual nil dereference happen.

func (n Name) Apply(t *Thing) { optional.Must(t).name = string(n) }

aThing := optional.Must[Thing](nil) // &Thing{} — never nil
aThing.name = "private pointer instance"

Configuration maps

For dynamic, string-keyed configuration, Configuration is an option-applied map[string]any that decodes into a struct via mapstructure. Declare typed ConfigurationKey[T] keys, turn them into options with .Opt(value) (or Enabled for a bool flag), build the map with NewConfiguration, and decode it with Decode:

package main

import (
	"fmt"
	"math"

	optional "github.com/gomatic/go-optional"
)

const (
	userKey    optional.ConfigurationKey[string] = "user"
	enabledKey optional.ConfigurationKey[bool]   = "enabled"
	countKey   optional.ConfigurationKey[int]    = "count"
	amountKey  optional.ConfigurationKey[float64] = "amount"
)

type Config struct {
	User    string  `mapstructure:"user"`
	Enabled bool    `mapstructure:"enabled"`
	Count   int     `mapstructure:"count"`
	Amount  float64 `mapstructure:"amount"`
}

func main() {
	cfg, err := optional.Decode[Config](optional.NewConfiguration(
		optional.Enabled(enabledKey),
		userKey.Opt("test-string"),
		countKey.Opt(99),
		amountKey.Opt(math.Pi),
	))
	fmt.Printf("%v %#v\n", err, cfg)
	// <nil> &main.Config{User:"test-string", Enabled:true, Count:99, Amount:3.141592653589793}
}

KV is a shorthand for building an Opt from a raw string key and value without declaring a typed ConfigurationKey first. A failed decode returns an error that matches the sentinel ErrDecode under errors.Is, wrapping the underlying mapstructure failure with %w.

Generic option-applied maps

Map[K, V] is the fully generic form behind Configuration: an option-applied map[K]V whose option is a Pair[K, V] carrying one key and value. Use it when you need a configurable map keyed by something other than string.

m := optional.New[optional.Map[string, int]](
	optional.Pair[string, int]{Key: "port", Value: 8080},
)

Design

Who uses it

go-optional underpins the friendly constructor APIs across the gomatic Go projects — see the other gomatic/go-* libraries.