-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoption.go
179 lines (162 loc) · 4.71 KB
/
option.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package opzione
import (
"reflect"
)
// Option is an optional type which not only checks if the stored value
// is present, but also tracks nested references and their changes.
//
// a := 10
// p := &a
// opt := Some(&p)
//
// p = nil
// println(opt.IsNone()) // true
//
// The tracking achieved using reflection, which may introduce overhead,
// but best effort has been made to keep the use of reflection to minimum.
//
// For value types and single pointers, Option skips recursive nil checks.
// Option does not track unsafe pointers, either, as they can be manipulated
// and interpreted arbitrarily.
type Option[T any] struct {
v *T
ptrtyp bool
track bool
validfn func(T) bool
}
// Validate adds custom validation logic when deciding whether the Option's
// inner value is meaningful or not. The value will be considered "none" if
// f returns true. The validation function is executed only after all nil
// checks are done.
func (o *Option[T]) Validate(f func(T) bool) {
o.validfn = f
}
// IsNone reports whether the Option contains no value, or contains merely
// a nil pointer or nested pointers to a nil reference.
func (o *Option[T]) IsNone() bool {
if o.v == nil {
return true
}
ok := false
if o.ptrtyp {
val := reflect.ValueOf(*o.v)
if o.track {
ok = isnil(val)
}
ok = val.IsNil()
}
if ok {
return true
}
if o.validfn != nil {
return o.validfn(*o.v)
}
return false
}
// Value attempts to retrieve the contained value. If the Option contains no value,
// is a nil pointer or nested pointers to nil, it will return ErrNoneOptional.
func (o *Option[T]) Value() (t T, err error) {
if o.IsNone() {
return t, ErrNoneOptional
}
return *o.v, nil
}
// Unwrap returns the contained value, panicking if the Option contains no
// meaningful value.
func (o *Option[T]) Unwrap() T {
if o.IsNone() {
panic(ErrNoneOptional)
}
return *o.v
}
// Swap swaps the contained value with v, returning the original value. If v is
// a nil pointer or dereferences to nil, the Option will be put in a "none" state
// such that subsequent calls to IsNone will return true. Whether the returned
// value is valid is not guaranteed; if the optional previously contains no
// meaningful value, it can be the zero value of the type, or nil.
func (o *Option[T]) Swap(v T) (t T) {
t = *o.v
o.v = &v
return
}
// Take moves the inner value out, leaving the optional in a "none" state such
// that subsequent calls to IsNone returns true. It returns a reference to the
// contained value, if any. Should the optional previously contains no meaningful
// value, ErrNoneOptional is returned; to forcibly move out an invalid pointer
// or value, consider calling Swap with nil.
func (o *Option[T]) Take() (*T, error) {
if o.IsNone() {
return nil, ErrNoneOptional
}
p := o.v
o.v = nil
return p, nil
}
// With executes the given closure, if the Option contains a meaningful value,
// with the contained value.
func (o *Option[T]) With(f func(T)) {
if !o.IsNone() {
f(*o.v)
}
}
// WithNone executes the given closure only if the Option contains no value.
func (o *Option[T]) WithNone(f func()) {
if o.IsNone() {
f()
}
}
// Assign assigns the inner value of the Option to *p, if it contains meaningful
// value. It returns a boolean indicating whether an assignment is made.
func (o *Option[T]) Assign(p **T) bool {
if o.IsNone() {
return false
}
*p = o.v
return true
}
func isptr[T any](t T) (reflect.Value, bool) {
val := reflect.ValueOf(t)
if !val.IsValid() {
panic("cannot determine t; invalid value detected")
}
return val, isptrkind(val.Kind())
}
func isptrkind(kind reflect.Kind) bool {
return kind == reflect.UnsafePointer ||
kind == reflect.Pointer ||
kind == reflect.Func ||
kind == reflect.Map ||
kind == reflect.Chan ||
kind == reflect.Interface
}
func isnil(val reflect.Value) bool {
if !val.IsValid() {
// val is constructed from empty Value{}, nil, or is corrupted.
return true
}
switch val.Kind() {
case reflect.UnsafePointer:
// An unsafe pointer can be anything; the package is only responsible
// for checking the shallowest reference.
return val.UnsafePointer() == nil
case reflect.Pointer:
elem := val.Elem()
if !elem.IsValid() {
// The pointer dereferences to nil; p := &i where i is nil.
return true
}
// Continue this process with the pointed object.
return isnil(elem)
case reflect.Func, reflect.Map, reflect.Chan, reflect.Interface:
// These are pointer-like types. They can be nil and calling a nil
// value may trigger a runtime panic.
return val.IsNil()
case reflect.Slice:
// A nil slice is safe to use. In the context of this package, we
// don't consider it purely "nil" as opposed to a pointer.
return false
default:
// Value types; cannot be nil.
return false
}
}