option

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 21, 2026 License: MIT Imports: 1 Imported by: 0

README

For the full discussion of nil safety and Tony Hoare's "billion-dollar mistake," see Nil Safety in Go.

An option is a container type that conditionally holds a single value. It is useful for two purposes:

  • enforcing a protocol that checks to see whether a value exists before attempting to use the value, preventing logical errors and runtime panics.
  • writing expressive code that takes into account whether or not a value exists without requiring conditional branches and the corresponding verbosity.

Creating Options

The option type is option.Basic[T any], where T is the type of the contained value. An option is considered ok if it contains a value and not-ok if it doesn’t.

The zero value is automatically not-ok:

notOkOption := option.Basic[string]{}

For most of Go’s built-in types, there are pre-made package variables of not-ok instances. The variables are:

  • option.NotOkAny
  • option.NotOkBool
  • option.NotOkByte
  • option.NotOkError
  • option.NotOkInt
  • option.NotOkRune
  • option.NotOkString

For those not included or for your own types, you may want to define a not-ok variable of your own, which can be done as a zero value above, or using the NotOk[T] factory:

myOption := option.NotOk[string]()

To make an ok option, use option.Of:

okOption := option.Of("hello world")

To make an option whose validity is dynamic, use option.New:

myOption := option.New(myString, ok) // option is "ok" if ok is true

A not-ok option never has a value, so if ok is false, the value for myString is discarded.

For comparable types, you may want to make an ok option only if a value has been provided. Since Go initializes variables to a zero value, let’s say you know the option should be not-ok if the variable has the zero value, since it hasn’t been assigned a value since initialization:

notOkOption := option.IfProvided("") // empty is the zero value for strings

Converting Pseudo-Options

Convert opts to options and vice-versa. nil pointers become not-ok. The value of an ok option is the value pointed at by the pointer, not the pointer itself:

message := "howdy"
messageOpt := &message // ok pseudo-option
okOption := option.FromOpt(messageOpt) // contains string not *string

messageOpt = nil // not-ok
notOkOption := option.FromOpt(messageOpt)

messageOpt = notOkOption.ToOpt() // messageOpt gets nil

Converting Non-Comparable Zero Values

For types that cannot use IfProvided because they contain slices, maps, or funcs (which are not comparable), implement the ZeroChecker interface:

type Registry struct {
    instances map[string]Instance
}

func (r Registry) IsZero() bool { return r.instances == nil }

// Now you can use IfNotZero
okOption := option.IfNotZero(Registry{instances: make(map[string]Instance)})
notOkOption := option.IfNotZero(Registry{}) // zero value has nil map

This mirrors IfProvided but works with any type that can report its own zero-ness.

Using the Option

Filtering

Limit the option to a range of values with KeepOkIf or ToNotOkIf, which return an option of the same type, just not-ok if the value doesn’t cause the argument function to return true.

These only make sense to use on options that might be ok.

func IsNotEmpty(s string) bool {
    return s != ""
}

okOption := option.Of("hello").KeepOkIf(IsNotEmpty)
notOkOption := option.Of("hello").ToNotOkIf(IsNotEmpty)

Mapping

There are To[Type] methods for mapping a contained value to the basic built-in types, which return an option of the type named in the method:

stringOption := option.Of(3).ToString(strconv.Itoa)

The types on the methods are the same as for the package variables, plus Convert to map to the same type as the existing value:

  • Convert
  • ToAny
  • ToBool
  • ToByte
  • ToError
  • ToInt
  • ToRune
  • ToString

Since methods cannot be generic in Go, there is no general Map method. To map to one of the other built-in types or a named type, there is a generic Map function instead:

stringOption := option.Map(option.Of(3), strconv.Itoa)

Working with the Value

If you need to obtain the value and work with it directly, Get returns the potential value and whether it is ok in Go’s comma-ok idiom:

if value, ok := myOption.Get(); ok {
    // work with value
}

While many things can be accomplished without unpacking the option, this is the easiest way to get started with options if you’re not familiar with FP.

It’s also possible to test for the presence of value:

ok := myOption.IsOk()

If you have tested it, or if your program requires the presence of a value as an invariant, you can get the value and panic if it is not there:

value := myOption.MustGet()

Apply a function to the value (if ok) for its side effect:

option.Of("hello world").Call(lof.Println) // print "hello world"
option.NotOkString.Call(lof.Println) // not called

If you have an alternative value such as a default, you can get the value, or the alternative if the value is not-ok:

three := option.Of(3).Or(4)
four := option.NotOkInt.Or(4)

If you are looking for the zero value of the value’s type as an alternative, there are a few methods that mean the same thing:

zero := option.NotOkInt.OrZero()
empty := option.NotOkString.OrEmpty()
False := option.NotOkBool.OrFalse()

Produce an expensive-to-compute alternative:

expensiveValue := option.NotOkInt.OrCall(ExpensiveCalculation)

You're ready to use options!

Patterns

These patterns demonstrate idiomatic usage drawn from production code.

Domain Option Types

Embed option.Basic in a custom struct for domain-specific option types:

type UserOption struct {
    option.Basic[User]
}

func UserOptionOf(u User) UserOption {
    return UserOption{Basic: option.Of(u)}
}

This allows adding domain-specific methods that work on the contained value.

Delegating Methods with Ok-Check

When you have a domain option type, add methods that delegate to the contained value:

func (o UserOption) IsActive() option.Bool {
    user, ok := o.Get()
    if !ok {
        return option.NotOkBool
    }
    return option.BoolOf(user.IsActive())
}

This propagates "not-ok" through the call chain - if the user doesn't exist, the result is also not-ok.

Tri-State with option.Bool

Use option.Bool when you need true/false/unknown semantics:

type ScanResult struct {
    IsConnected option.Bool  // true, false, or unknown (not-ok)
    IsMigrated  option.Bool
}

// Usage with default:
connected := result.IsConnected.OrFalse()  // unknown becomes false

IfProvided for Nullable Database Fields

Convert nullable strings to options cleanly:

type Record struct {
    NullableHost sql.NullString `db:"host"`
}

func (r Record) GetHost() option.String {
    return option.IfProvided(r.NullableHost.String)
}

Documentation

Overview

Package option provides types and functions to work with optional values.

Index

Constants

This section is empty.

Variables

View Source
var (
	NotOkAny    = Basic[any]{}
	NotOkBool   = Basic[bool]{}
	NotOkByte   = Basic[byte]{}
	NotOkError  = Basic[error]{}
	NotOkInt    = Basic[int]{}
	NotOkRune   = Basic[rune]{}
	NotOkString = Basic[string]{}
)

Functions

This section is empty.

Types

type Any

type Any = Basic[any]

type Basic

type Basic[T any] struct {
	// contains filtered or unexported fields
}

Basic represents an optional value of type T.

func FromOpt

func FromOpt[T any](t *T) (_ Basic[T])

FromOpt returns an ok option of *what t points at* provided that t is not nil, or not-ok otherwise. It is useful to convert a pseudo-option based on pointers into a formal option. By convention, in consuming code, we suffix a pseudo-option's variable name with an "Opt" suffix to clarify intent, hence "FromOpt".

func IfNotZero added in v0.7.0

func IfNotZero[T ZeroChecker](t T) (_ Basic[T])

IfNotZero returns an ok option of t provided that t.IsZero() returns false, or not-ok otherwise. It is the complement to IfProvided for non-comparable types. Use this when a type has fields that prevent it from being comparable (slices, maps, funcs).

func IfProvided

func IfProvided[T comparable](t T) (_ Basic[T])

IfProvided returns an ok option of t provided that t is not the zero value for T, or not-ok otherwise.

func Map

func Map[T, R any](b Basic[T], fn func(T) R) (_ Basic[R])

func New

func New[T any](t T, ok bool) (_ Basic[T])

New returns an ok option of t provided that ok is true, or not-ok otherwise.

func NotOk

func NotOk[T any]() (_ Basic[T])

func Of

func Of[T any](t T) Basic[T]

Of returns an ok option of t, independent of t's value.

func (Basic[T]) Call

func (b Basic[T]) Call(fn func(T))

Call applies fn to the option's value provided that the option is ok.

func (Basic[T]) Get

func (b Basic[T]) Get() (_ T, _ bool)

Get returns the option's value and a boolean indicating the option's status. It unpacks the option's fields into Go's comma-ok idiom, making it useful in the usual Go conditional constructs. When used in this manner, myVal doesn't stick around in the namespace when you're done with it:

if myVal, ok := o.Get; ok {
  do some stuff
}

func (Basic[T]) IsOk

func (b Basic[T]) IsOk() bool

IsOk returns true if the option is ok.

func (Basic[T]) KeepOkIf

func (b Basic[T]) KeepOkIf(fn func(T) bool) (_ Basic[T])

KeepOkIf returns b provided that applying fn to an ok option's value returns true, or the original option otherwise. It is the filter operation. Since Go doesn't offer a convenient lambda syntax for constructing the negation of a function's output, there is a ToNotOkIf method as well.

func (Basic[T]) MustGet

func (b Basic[T]) MustGet() T

MustGet returns the option's value or panics if the option is not ok.

func (Basic[T]) Or

func (b Basic[T]) Or(t T) T

Or returns the option's value provided that the option is ok, otherwise t.

func (Basic[T]) OrCall

func (b Basic[T]) OrCall(fn func() T) (_ T)

OrCall returns the option's value provided that it is ok, otherwise the result of calling fn.

func (Basic[T]) OrEmpty

func (b Basic[T]) OrEmpty() (_ T)

OrEmpty returns the option's value provided that it is ok, otherwise the zero value for T. It is a more readable alias for OrZero when T is string.

func (Basic[T]) OrFalse

func (b Basic[T]) OrFalse() (_ T)

OrFalse returns the option's value provided that it is ok, otherwise the zero value for T. It is a more readable alias for OrZero when T is bool.

func (Basic[T]) OrZero

func (b Basic[T]) OrZero() (_ T)

OrZero returns the option's value provided that it is ok, otherwise the zero value for T. See OrEmpty and OrFalse for more readable aliases of OrZero when T is string or bool.

func (Basic[T]) ToAny

func (b Basic[T]) ToAny(fn func(T) any) (_ Basic[any])

ToAny returns an option of the result of applying fn to the option's value provided that the option is ok, or not-ok otherwise.

func (Basic[T]) ToBool

func (b Basic[T]) ToBool(fn func(T) bool) (_ Basic[bool])

ToBool returns an option of the result of applying fn to the option's value provided that the option is ok, or not-ok otherwise.

func (Basic[T]) ToByte

func (b Basic[T]) ToByte(fn func(T) byte) (_ Basic[byte])

ToByte returns an option of the result of applying fn to the option's value provided that the option is ok, or not-ok otherwise.

func (Basic[T]) ToError

func (b Basic[T]) ToError(fn func(T) error) (_ Basic[error])

ToError returns an option of the result of applying fn to the option's value provided that the option is ok, or not-ok otherwise.

func (Basic[T]) ToInt

func (b Basic[T]) ToInt(fn func(T) int) (_ Basic[int])

ToInt returns an option of the result of applying fn to the option's value provided that the option is ok, or not-ok otherwise.

func (Basic[T]) ToNotOkIf

func (b Basic[T]) ToNotOkIf(fn func(T) bool) (_ Basic[T])

ToNotOkIf returns a not-ok option provided that applying fn to an ok option's value returns true, or the original option otherwise. It is the filter operation with negation. Since Go doesn't offer a convenient lambda syntax for constructing the negation of a function's output, having negation built-in is both a convenience and keeps consuming code readable.

func (Basic[T]) ToOpt

func (b Basic[T]) ToOpt() (_ *T)

ToOpt returns a pointer-based pseudo-option of the pointed-at value provided that the option is ok, or not-ok otherwise. By convention, in consuming code, we suffix a pseudo-option's variable name with an "Opt" suffix to clarify the pointer's meaning and use, hence "ToOpt".

func (Basic[T]) ToRune

func (b Basic[T]) ToRune(fn func(T) rune) (_ Basic[rune])

ToRune returns an option of the result of applying fn to the option's value provided that the option is ok, or not-ok otherwise.

func (Basic[T]) ToSame

func (b Basic[T]) ToSame(fn func(T) T) (_ Basic[T])

ToSame returns the result of applying fn to the option's value provided that the option is ok, or not-ok otherwise.

func (Basic[T]) ToString

func (b Basic[T]) ToString(fn func(T) string) (_ Basic[string])

ToString returns an option of the result of applying fn to the option's value provided that the option is ok, or not-ok otherwise.

type Bool

type Bool = Basic[bool]

type Byte

type Byte = Basic[byte]

type Error

type Error = Basic[error]

type Int

type Int = Basic[int]

type Rune

type Rune = Basic[rune]

type String

type String = Basic[string]

func Getenv

func Getenv(key string) String

type ZeroChecker added in v0.7.0

type ZeroChecker interface {
	IsZero() bool
}

ZeroChecker is implemented by types that can report whether they are their zero value. This enables IfNotZero to work with non-comparable types (structs with slices, maps, or funcs).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL