opt

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Dec 29, 2025 License: MIT Imports: 8 Imported by: 0

README

Go Reference

opt

Optionality is often implicitly represented by overloading the semantics of pointers or zeroness. This package exports the opt.T type and aliases for built-in types, to explicitly represent optional values.

Install

go get github.com/lukasngl/opt

Requires Go 1.22+.

Usage

type Query struct {
    OrderBy opt.T[OrderBy] `json:"orderBy,omitzero"`
    Limit   opt.Int        `json:"limit,omitzero"`
    Offset  opt.Int        `json:"offset,omitzero"`
}
value, ok := optional.Get()
if !ok {
    return fmt.Errorf("not present")
}

Features

  • Concise: opt.T[V] and aliases like opt.Int keep declarations short.

  • Idiomatic: Get() returns (value, ok), matching Go patterns like map lookup and type assertions.

  • Compatible:

    • Pointers: Convert with FromPtr and ToPtr
    • Zero values: Convert with FromZeroable, honoring IsZero() methods
    • JSON: Implements json.Marshaler/Unmarshaler, with omitzero support (go1.24)
    • SQL: Implements driver.Valuer/sql.Scanner via sql.Null[V]
    • Iterators: All() returns iter.Seq[V] (Go 1.23+)
  • Functional utilities: Map, FlatMap, Filter, Or, OrElseGet, IfPresent — for when you have a function ready to pass. Otherwise if v, ok := o.Get(); ok is usually cleaner.

Prior Art

There are loads of other optional type packages, but all of them had at least one of the following deal-breakers:

  • Really long type names — the TypeScript folks just suffix a question mark, so why should we suffer?
  • A slice-based approach — forces the value onto the heap and stores length and capacity, leading to unnecessary overhead.
  • No IsZero() bool method for the omitzero tag.

Documentation

Overview

Package opt provides a generic optional type for Go.

An option represents a value that may or may not be present, providing a type-safe alternative to nil pointers or sentinel values.

Basic usage:

name := opt.Some("Alice")
missing := opt.None[string]()

// Access with comma-ok idiom
if v, ok := name.Get(); ok {
    fmt.Println(v)
}

// Or use default values
fmt.Println(missing.OrElse("unknown"))

Options integrate with JSON (marshals to null when empty) and database/sql.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Bool

type Bool = T[bool]

Alias for the builtin type.

type Byte

type Byte = T[byte]

Alias for the builtin type.

type Complex128

type Complex128 = T[complex128]

Alias for the builtin type.

type Complex64

type Complex64 = T[complex64]

Alias for the builtin type.

type Error

type Error = T[error]

Alias for the builtin type.

type Float32

type Float32 = T[float32]

Alias for the builtin type.

type Float64

type Float64 = T[float64]

Alias for the builtin type.

type Int added in v0.1.0

type Int = T[int]

Alias for the builtin type.

type Int16

type Int16 = T[int16]

Alias for the builtin type.

type Int32

type Int32 = T[int32]

Alias for the builtin type.

type Int64

type Int64 = T[int64]

Alias for the builtin type.

type Int8

type Int8 = T[int8]

Alias for the builtin type.

type Rune

type Rune = T[rune]

Alias for the builtin type.

type String

type String = T[string]

Alias for the builtin type.

type T

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

T represents an option, i.e. a value that may be empty or present.

func FlatMap added in v0.1.0

func FlatMap[V, U any](t T[V], fn func(V) T[U]) T[U]

FlatMap transforms the value inside the option using the provided function that returns an option. If the option is empty, returns an empty option. Use FlatMap to chain operations that may fail.

parseInt := func(s string) opt.T[int] { ... }
opt.FlatMap(opt.Some("42"), parseInt)  // Some(42)
opt.FlatMap(opt.Some("xx"), parseInt)  // None
Example
package main

import (
	"fmt"
	"strconv"

	"github.com/lukasngl/opt"
)

func main() {
	parseInt := func(s string) opt.T[int] {
		n, err := strconv.Atoi(s)
		if err != nil {
			return opt.None[int]()
		}
		return opt.Some(n)
	}

	valid := opt.FlatMap(opt.Some("42"), parseInt)
	invalid := opt.FlatMap(opt.Some("abc"), parseInt)

	fmt.Println(valid)
	fmt.Println(invalid)
}
Output:

Some[int](42)
None[int]()

func FromPtr added in v0.1.0

func FromPtr[V any](value *V) T[V]

FromPtr creates a new option from a pointer.

If the pointer is nil an empty option is returned, otherwise the value referenced by the pointer will be used as the value for a new present option.

Inverse of T.ToPtr.

Example (Nil)
package main

import (
	"fmt"
	"time"

	"github.com/lukasngl/opt"
)

func main() {
	fmt.Printf("%s\n", opt.FromPtr((*time.Time)(nil)))
}
Output:

None[time.Time]()
Example (NotZero)
package main

import (
	"fmt"
	"time"

	"github.com/lukasngl/opt"
)

func main() {
	// Time that is not zero
	value := time.Time{}.Add(time.Hour + time.Minute + time.Second)

	fmt.Printf("%s\n", opt.FromPtr(&value))

}
Output:

Some[time.Time](0001-01-01 01:01:01 +0000 UTC)
Example (Zero)
package main

import (
	"fmt"
	"time"

	"github.com/lukasngl/opt"
)

func main() {
	// Time that is zero, but not the zero value.
	value := time.Time{}.In(time.FixedZone("XTC", 42))

	fmt.Printf("%s\n", opt.FromPtr(&value))
}
Output:

Some[time.Time](0001-01-01 00:00:42 +0000 XTC)
Example (ZeroValue)
package main

import (
	"fmt"
	"time"

	"github.com/lukasngl/opt"
)

func main() {
	// Time that is zero and the zero value.
	value := time.Time{}

	fmt.Printf("%s\n", opt.FromPtr(&value))
}
Output:

Some[time.Time](0001-01-01 00:00:00 +0000 UTC)

func FromZeroable

func FromZeroable[V any](value V) T[V]

FromZeroable creates a new option from a value.

If the value is zero, an empty option is returned, otherwise a present option containing the option is returned.

Zeroness is determined as follows:

  1. If the value has an "IsZero() bool" method, it is used to determine zeroness,
  2. otherwise [reflect.Value#IsZero] is used.
Example (Nil)
package main

import (
	"fmt"
	"time"

	"github.com/lukasngl/opt"
)

func main() {
	fmt.Printf("%s\n", opt.FromZeroable((*time.Time)(nil)))
}
Output:

None[*time.Time]()
Example (NotZero)
package main

import (
	"fmt"
	"time"

	"github.com/lukasngl/opt"
)

func main() {
	// Time that is not zero
	value := time.Time{}.Add(time.Hour + time.Minute + time.Second)

	fmt.Printf("%s\n", opt.FromZeroable(value))
	fmt.Printf("%s\n", opt.FromZeroable(&value))
}
Output:

Some[time.Time](0001-01-01 01:01:01 +0000 UTC)
Some[*time.Time](0001-01-01 01:01:01 +0000 UTC)
Example (Zero)
package main

import (
	"fmt"
	"time"

	"github.com/lukasngl/opt"
)

func main() {
	// Time that is zero, but not the zero value.
	value := time.Time{}.In(time.FixedZone("XTC", 42))

	fmt.Printf("%s\n", opt.FromZeroable(value))
	fmt.Printf("%s\n", opt.FromZeroable(&value))
}
Output:

None[time.Time]()
None[*time.Time]()
Example (ZeroValue)
package main

import (
	"fmt"
	"time"

	"github.com/lukasngl/opt"
)

func main() {
	// Time that is zero and the zero value.
	value := time.Time{}

	fmt.Printf("%s\n", opt.FromZeroable(value))
	fmt.Printf("%s\n", opt.FromZeroable(&value))
}
Output:

None[time.Time]()
None[*time.Time]()

func Map added in v0.1.0

func Map[V, U any](t T[V], fn func(V) U) T[U]

Map transforms the value inside the option using the provided function. If the option is empty, returns an empty option.

opt.Map(opt.Some(2), func(n int) int { return n * 2 })  // Some(4)
opt.Map(opt.Some("hi"), strings.ToUpper)               // Some("HI")
Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	o := opt.Some(21)
	doubled := opt.Map(o, func(n int) int { return n * 2 })

	fmt.Println(doubled)
}
Output:

Some[int](42)

func New added in v0.1.0

func New[V any](value V, present bool) T[V]

New creates a new option from a value and a boolean indicating whether the value is present.

It is the inverse of T.Get.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	// New is the inverse of Get, useful for wrapping comma-ok results
	m := map[string]int{"a": 1}
	v, ok := m["a"]
	o := opt.New(v, ok)

	fmt.Println(o)
}
Output:

Some[int](1)

func None

func None[V any]() T[V]

None creates a new empty option.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	o := opt.None[string]()
	fmt.Println(o)
}
Output:

None[string]()

func Some

func Some[V any](value V) T[V]

Some creates a new present option, that contains the given value.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	o := opt.Some(42)
	fmt.Println(o)
}
Output:

Some[int](42)

func (T[V]) All added in v0.1.0

func (t T[V]) All() iter.Seq[V]

All returns an iterator that yields the value if present. This allows using an option in a for-range loop:

for v := range opt.All() {
    // v is the unwrapped value
}

func (T[V]) Filter added in v0.1.0

func (t T[V]) Filter(predicate func(V) bool) T[V]

Filter returns the option if present and the predicate returns true, otherwise returns None.

opt.Some(42).Filter(func(n int) bool { return n > 0 })  // Some(42)
opt.Some(-1).Filter(func(n int) bool { return n > 0 })  // None
Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	positive := opt.Some(42).Filter(func(n int) bool { return n > 0 })
	negative := opt.Some(-1).Filter(func(n int) bool { return n > 0 })

	fmt.Println(positive)
	fmt.Println(negative)
}
Output:

Some[int](42)
None[int]()

func (T[V]) Generate

func (t T[V]) Generate(rand *rand.Rand, _ int) reflect.Value

Generate implements quick.Generator.

func (T[V]) Get added in v0.1.0

func (t T[V]) Get() (V, bool)

Get returns the wrapped value and whether it is present.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	something := opt.Some("hello")

	if value, present := something.Get(); present {
		fmt.Printf("%s unwrapped to %s", something, value)
	}
}
Output:

Some[string](hello) unwrapped to hello

func (T[V]) IfPresent added in v0.1.0

func (t T[V]) IfPresent(fn func(V))

IfPresent calls fn with the wrapped value if present.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	opt.Some("hello").IfPresent(func(s string) {
		fmt.Println("got:", s)
	})
	opt.None[string]().IfPresent(func(s string) {
		fmt.Println("got:", s) // not called
	})
}
Output:

got: hello

func (T[V]) IsEmpty

func (t T[V]) IsEmpty() bool

IsEmpty returns whether the option is empty.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	some := opt.Some(42)
	none := opt.None[int]()

	fmt.Println(some.IsEmpty(), none.IsEmpty())
}
Output:

false true

func (T[V]) IsPresent

func (t T[V]) IsPresent() bool

IsPresent returns whether the option is present.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	some := opt.Some(42)
	none := opt.None[int]()

	fmt.Println(some.IsPresent(), none.IsPresent())
}
Output:

true false

func (T[V]) IsZero

func (t T[V]) IsZero() bool

IsZero returns whether the option is empty. From go1.24 this can be used with omitzero struct tag.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	some := opt.Some(42)
	none := opt.None[int]()

	fmt.Println(some.IsZero(), none.IsZero())
}
Output:

false true

func (T[V]) MarshalJSON

func (t T[V]) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (T[V]) Must

func (t T[V]) Must() V

Must returns the wrapped value and panics if the github.com/lukasngl/opt.T is empty.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	o := opt.Some(42)
	fmt.Println(o.Must())
}
Output:

42

func (T[V]) Or added in v0.1.0

func (t T[V]) Or(other T[V]) T[V]

Or returns the option if present, otherwise returns other.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	primary := opt.None[string]()
	fallback := opt.Some("default")

	fmt.Println(primary.Or(fallback))
}
Output:

Some[string](default)

func (T[V]) OrElse

func (t T[V]) OrElse(defaultValue V) V

OrElse returns the wrapped value if not empty and the given default value otherwise.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	something := opt.Some("hello")
	nothing := opt.None[string]()

	fmt.Printf("%s %s",
		something.OrElse("bye"),
		nothing.OrElse("world!"),
	)
}
Output:

hello world!

func (T[V]) OrElseGet added in v0.1.0

func (t T[V]) OrElseGet(fn func() V) V

OrElseGet returns the wrapped value if present, otherwise calls fn and returns its result.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	none := opt.None[int]()
	result := none.OrElseGet(func() int {
		return 100 // expensive computation
	})

	fmt.Println(result)
}
Output:

100

func (T[V]) OrZero

func (t T[V]) OrZero() V

OrZero returns the wrapped value if not empty and the zero value otherwise.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	nothing := opt.None[string]()

	fmt.Printf("%q", nothing.OrZero())
}
Output:

""

func (*T[V]) Scan

func (t *T[V]) Scan(src any) error

Scan implements sql.Scanner.

func (T[V]) String

func (t T[V]) String() string

String implements fmt.Stringer.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	some := opt.Some("hello")
	none := opt.None[string]()

	fmt.Println(some.String())
	fmt.Println(none.String())
}
Output:

Some[string](hello)
None[string]()

func (T[V]) ToPtr added in v0.1.0

func (t T[V]) ToPtr() *V

ToPtr returns a pointer to the wrapped value if present, otherwise a nil pointer.

Inverse of FromPtr.

Example
package main

import (
	"fmt"

	"github.com/lukasngl/opt"
)

func main() {
	some := opt.Some(42)
	none := opt.None[int]()

	fmt.Println(*some.ToPtr())
	fmt.Println(none.ToPtr())
}
Output:

42
<nil>

func (*T[V]) UnmarshalJSON

func (t *T[V]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

func (T[V]) Value

func (t T[V]) Value() (driver.Value, error)

Value implements driver.Valuer.

type Uint added in v0.1.0

type Uint = T[uint]

Alias for the builtin type.

type Uint16

type Uint16 = T[uint16]

Alias for the builtin type.

type Uint32

type Uint32 = T[uint32]

Alias for the builtin type.

type Uint64

type Uint64 = T[uint64]

Alias for the builtin type.

type Uint8

type Uint8 = T[uint8]

Alias for the builtin type.