|
| 1 | +// Package option implements [option types] in Go. |
| 2 | +// It takes inspiration from [samber/mo] but also works with BSON and exposes |
| 3 | +// a (hopefully) more refined interface. |
| 4 | +// |
| 5 | +// Option types facilitate avoidance of nil-dereference bugs, at the cost of a |
| 6 | +// bit more overhead. |
| 7 | +// |
| 8 | +// A couple special notes: |
| 9 | +// - nil values inside the Option, like `Some([]int(nil))`, are forbidden. |
| 10 | +// - Option’s BSON marshaling/unmarshaling interoperates with the [bson] |
| 11 | +// package’s handling of nilable pointers. So any code that uses nilable |
| 12 | +// pointers to represent optional values can switch to Option and |
| 13 | +// should continue working with existing persisted data. |
| 14 | +// - Because encoding/json provides no equivalent to bsoncodec.Zeroer, |
| 15 | +// Option always marshals to JSON null if empty. |
| 16 | +// |
| 17 | +// Prefer Option to nilable pointers in all new code, and consider |
| 18 | +// changing existing code to use it. |
| 19 | +// |
| 20 | +// [option types]: https://en.wikipedia.org/wiki/Option_type |
| 21 | +package option |
| 22 | + |
| 23 | +import ( |
| 24 | + "fmt" |
| 25 | + "reflect" |
| 26 | + |
| 27 | + "go.mongodb.org/mongo-driver/bson" |
| 28 | + "go.mongodb.org/mongo-driver/bson/bsoncodec" |
| 29 | +) |
| 30 | + |
| 31 | +var _ bson.ValueMarshaler = &Option[int]{} |
| 32 | +var _ bson.ValueUnmarshaler = &Option[int]{} |
| 33 | +var _ bsoncodec.Zeroer = &Option[int]{} |
| 34 | + |
| 35 | +// Option represents a possibly-empty value. |
| 36 | +// Its zero value is the empty case. |
| 37 | +type Option[T any] struct { |
| 38 | + val *T |
| 39 | +} |
| 40 | + |
| 41 | +// Some creates an Option with a value. |
| 42 | +func Some[T any](value T) Option[T] { |
| 43 | + if isNil(value) { |
| 44 | + panic(fmt.Sprintf("Option forbids nil value (%T).", value)) |
| 45 | + } |
| 46 | + |
| 47 | + return Option[T]{&value} |
| 48 | +} |
| 49 | + |
| 50 | +// None creates an Option with no value. |
| 51 | +// |
| 52 | +// Note that `None[T]()` is interchangeable with `Option[T]{}`. |
| 53 | +func None[T any]() Option[T] { |
| 54 | + return Option[T]{} |
| 55 | +} |
| 56 | + |
| 57 | +// FromPointer will convert a nilable pointer into its |
| 58 | +// equivalent Option. |
| 59 | +func FromPointer[T any](valPtr *T) Option[T] { |
| 60 | + if valPtr == nil { |
| 61 | + return None[T]() |
| 62 | + } |
| 63 | + |
| 64 | + if isNil(*valPtr) { |
| 65 | + panic(fmt.Sprintf("Given pointer (%T) refers to nil, which is forbidden.", valPtr)) |
| 66 | + } |
| 67 | + |
| 68 | + myCopy := *valPtr |
| 69 | + |
| 70 | + return Option[T]{&myCopy} |
| 71 | +} |
| 72 | + |
| 73 | +// IfNotZero returns an Option that’s populated if & only if |
| 74 | +// the given value is a non-zero value. (NB: The zero value |
| 75 | +// for slices & maps is nil, not empty!) |
| 76 | +// |
| 77 | +// This is useful, e.g., to interface with code that uses |
| 78 | +// nil to indicate a missing slice or map. |
| 79 | +func IfNotZero[T any](value T) Option[T] { |
| 80 | + |
| 81 | + // copied from samber/mo.EmptyableToOption: |
| 82 | + if reflect.ValueOf(&value).Elem().IsZero() { |
| 83 | + return Option[T]{} |
| 84 | + } |
| 85 | + |
| 86 | + return Option[T]{&value} |
| 87 | +} |
| 88 | + |
| 89 | +// Get “unboxes” the Option’s internal value. |
| 90 | +// The boolean indicates whether the value exists. |
| 91 | +func (o Option[T]) Get() (T, bool) { |
| 92 | + if o.val == nil { |
| 93 | + return *new(T), false |
| 94 | + } |
| 95 | + |
| 96 | + return *o.val, true |
| 97 | +} |
| 98 | + |
| 99 | +// MustGet is like Get but panics if the Option is empty. |
| 100 | +func (o Option[T]) MustGet() T { |
| 101 | + val, exists := o.Get() |
| 102 | + if !exists { |
| 103 | + panic(fmt.Sprintf("MustGet() called on empty %T", o)) |
| 104 | + } |
| 105 | + |
| 106 | + return val |
| 107 | +} |
| 108 | + |
| 109 | +// OrZero returns either the Option’s internal value or |
| 110 | +// the type’s zero value. |
| 111 | +func (o Option[T]) OrZero() T { |
| 112 | + val, exists := o.Get() |
| 113 | + if exists { |
| 114 | + return val |
| 115 | + } |
| 116 | + |
| 117 | + return *new(T) |
| 118 | +} |
| 119 | + |
| 120 | +// OrElse returns either the Option’s internal value or |
| 121 | +// the given `fallback`. |
| 122 | +func (o Option[T]) OrElse(fallback T) T { |
| 123 | + val, exists := o.Get() |
| 124 | + if exists { |
| 125 | + return val |
| 126 | + } |
| 127 | + |
| 128 | + return fallback |
| 129 | +} |
| 130 | + |
| 131 | +// ToPointer converts the Option to a nilable pointer. |
| 132 | +// The internal value (if it exists) is (shallow-)copied. |
| 133 | +func (o Option[T]) ToPointer() *T { |
| 134 | + val, exists := o.Get() |
| 135 | + if exists { |
| 136 | + theCopy := val |
| 137 | + return &theCopy |
| 138 | + } |
| 139 | + |
| 140 | + return nil |
| 141 | +} |
| 142 | + |
| 143 | +// IsNone returns a boolean indicating whether or not the option is a None |
| 144 | +// value. |
| 145 | +func (o Option[T]) IsNone() bool { |
| 146 | + return o.val == nil |
| 147 | +} |
| 148 | + |
| 149 | +// IsSome returns a boolean indicating whether or not the option is a Some |
| 150 | +// value. |
| 151 | +func (o Option[T]) IsSome() bool { |
| 152 | + return o.val != nil |
| 153 | +} |
0 commit comments