1 /** 2 Numem Optional type 3 4 Copyright: 5 Copyright © 2023-2025, Kitsunebi Games 6 Copyright © 2023-2025, Inochi2D Project 7 8 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 Authors: Luna Nielsen 10 */ 11 module numem.optional; 12 import numem.lifetime; 13 import numem.core.traits; 14 import numem.core.hooks; 15 16 /** 17 A struct which wraps a value and whether said value is "valid". 18 19 Example: 20 --- 21 Option!int positiveOnly(int value) @nogc nothrow { 22 return value >= 0 ? some(value) : none!int(); 23 } 24 25 auto rval = positiveOnly(4); 26 if (rval) { 27 writeln("Value is positive!"); 28 } else { 29 writeln("Value is negative!"); 30 } 31 --- 32 */ 33 struct Option(T) { 34 private: 35 @nogc: 36 static if (isHeapAllocated!T) { 37 T value_ = T.init; 38 } else { 39 bool state_ = false; 40 T value_ = T.init; 41 } 42 43 // Constructors 44 this(Y)(Y value) nothrow { 45 static if (isHeapAllocated!T) { 46 this.value_ = cast(T)cast(void*)value; 47 } else static if (is(Y == T)) { 48 this.value_ = value; 49 this.state_ = true; 50 } else { 51 this.state_ = false; 52 } 53 } 54 55 public: 56 alias hasValue this; // Allows using optional in if statements. 57 @disable this(); 58 59 /** 60 Wraps $(D value) in an optional type. 61 62 Params: 63 value = The value to wrap. 64 65 Returns: 66 The wrapped value. 67 */ 68 static Option!T some(T value) @trusted nothrow { 69 return Option!T(value); 70 } 71 72 /** 73 Creates a new empty value. 74 75 Returns: 76 The wrapped value. 77 */ 78 static Option!T none() { 79 return Option!T(null); 80 } 81 82 /** 83 Whether this instance contains a valid value. 84 */ 85 @property bool hasValue() @trusted pure nothrow { 86 static if (isHeapAllocated!T) 87 return value_ !is null; 88 else 89 return state_; 90 } 91 92 /** 93 Destroys the contained value. 94 */ 95 void reset() @trusted nothrow { 96 static if (isHeapAllocated!T) { 97 static if (hasAnyDestructor!T) 98 nogc_trydelete(this.value_); 99 else { 100 nu_free(this.value_); 101 } 102 this.value_ = null; 103 104 } else { 105 nogc_initialize(value_); 106 state_ = false; 107 } 108 } 109 110 /** 111 Gets the value stored within the Optional. 112 113 Returns: 114 The value stored within the Optional, 115 throws a $(D NuException) if the Optional is invalid. 116 */ 117 T get() @trusted { 118 if (!hasValue) 119 nu_fatal("No value contained within optional!"); 120 121 return value_; 122 } 123 124 /** 125 Gets the value stored within the Optional if valid, 126 otherwise returns the given value. 127 128 Params: 129 value = The value to return if the optional is invalid. 130 131 Returns: 132 The value stored within the Optional, 133 $(D value) otherwise. 134 */ 135 T getOr(T value = T.init) @trusted nothrow { 136 return hasValue ? value_ : value; 137 } 138 } 139 140 /** 141 Wraps value in an Optional type. 142 143 Params: 144 value = The value to wrap 145 146 Returns: 147 The value wrapped in an optional type. 148 $(D null) values will be seen as invalid. 149 */ 150 auto ref Option!T some(T)(auto ref T value) @trusted @nogc nothrow { 151 return Option!(T).some(value); 152 } 153 154 /** 155 Creates a "none" optional value type. 156 157 Returns: 158 An optional wrapped "none" type. 159 */ 160 Option!T none(T)() @trusted @nogc nothrow { 161 return Option!(T).none(); 162 } 163 164 /** 165 A type which wraps a value and a potential error. 166 */ 167 struct Result(T) { 168 private: 169 @nogc: 170 string error_ = "Generic error"; 171 T value_; 172 173 public: 174 alias isOK this; // Allows using Result in if statements. 175 176 /** 177 Whether the result is successful. 178 */ 179 @property bool isOK() @trusted nothrow pure => error_.length == 0; 180 181 /** 182 The error stored within the Result. 183 */ 184 @property string error() => error_; 185 186 /** 187 Creates an "ok" value. 188 189 Params: 190 value = The value to set 191 192 Returns: 193 A new result. 194 */ 195 static typeof(this) makeOk(T value) @trusted nothrow { 196 return typeof(this)(value_: value, error_: null); 197 } 198 199 /** 200 Creates an error value. 201 202 Params: 203 message = An UTF-8 encoded error message. 204 205 Returns: 206 A new result. 207 */ 208 static typeof(this) makeError(string message) @trusted nothrow { 209 return typeof(this)(error_: message); 210 } 211 212 /** 213 Gets the value stored within the Result. 214 215 Returns: 216 The value stored within the Result, 217 otherwise the application will crash with a fatal error. 218 */ 219 T get() @trusted nothrow { 220 if (error_.length > 0) 221 nu_fatal(error_); 222 223 return value_; 224 } 225 226 /** 227 Gets the value stored within the Result if non-error, 228 otherwise returns the given value. 229 230 Params: 231 value = The value to return if the Result is an error. 232 233 Returns: 234 The value stored within the Result, 235 $(D value) otherwise. 236 */ 237 T getOr(T value = T.init) @trusted nothrow { 238 return isOK ? value_ : value; 239 } 240 } 241 242 /** 243 Wraps a value into a $(D Result). 244 245 Params: 246 value = The value to wrap 247 248 Returns: 249 The value wrapped into a $(D Result) 250 */ 251 auto ref Result!T ok(T)(auto ref T value) @trusted @nogc nothrow { 252 return Result!(T).makeOk(value); 253 } 254 255 /** 256 Creates an error result. 257 258 Params: 259 message = The error message of the Result. 260 261 Returns: 262 A wrapped error. 263 */ 264 Result!T error(T)(string message) @trusted @nogc nothrow { 265 return Result!(T).makeError(message); 266 }