1 /** Wrapper type for a sum-type (union) of an unexpected and expected value. 2 */ 3 module nxt.expected; 4 5 /** Wrapper type for an unexpected value of type `E`. 6 */ 7 private struct Unexpected(E) 8 { 9 E value; 10 alias value this; 11 } 12 13 /** Union (sum) type of either an expected (most probable) value of type `T` or 14 * an unexpected value of type `E` (being an instance of type `Unexpected!E`). 15 * 16 * `E` is typically an error code (for instance C/C++'s' `errno` int) or a 17 * subclass of `Exception` (which is the default). 18 * 19 * See_Also: https://www.youtube.com/watch?v=nVzgkepAg5Y 20 * See_Also: https://github.com/dlang/phobos/pull/6665 21 * See_Also: https://code.dlang.org/packages/expectations 22 * See_Also: https://doc.rust-lang.org/std/result/ 23 * See_Also: https://github.com/tchaloupka/expected 24 * 25 * TODO: https://dlang.org/phobos/std_typecons.html#.apply 26 * 27 * TODO: I'm not convinced about the naming 28 * - `Expected`: instead call it something that tells us that it can be either expected or unexpected? 29 * - `Unexpected`: if so why shouldn't we have a similar value wrapper `Expected`? 30 * 31 * TODO: we could get around the `Unexpected` wrapper logic by instead expressing 32 * construction in static constructor functions, say,: 33 * - static typeof(this) fromExpectedValue(T expectedValue) 34 * - static typeof(this) fromUnexpectedValue(E unexpectedValue) 35 * 36 * TODO: swap 37 * 38 * TODO: which functions should be `nothrow`? 39 * 40 * TODO: later on: remove _ok when `_expectedValue` and ` _unexpectedValue` can store this state 41 * "collectively" for instance when both are pointers or classes (use trait 42 * `isAddress`) 43 * 44 * TODO: ok to default `E` to `Exception`? 45 */ 46 struct Expected(T, E = Exception) 47 if (!is(T == Unexpected!(_), _) && // an `Unexpected` cannot be `Expected` :) 48 !is(T == void)) // disallow void for now, for ref see https://forum.dlang.org/post/ncjhsxshttikzjqgiwev@forum.dlang.org 49 { 50 import core.lifetime : moveEmplace; 51 import nxt.container.traits : isAddress; 52 53 /+ TODO: ok for default construction to initialize +/ 54 // - _expectedValue = T.init (zeros) 55 // - _ok = true (better to have _isError so default is zero bits here aswell?) 56 57 /// Construct from expected value `expectedValue.` 58 this()(auto ref T expectedValue) @trusted 59 { 60 /+ TODO: reuse opAssign? +/ 61 moveEmplace(expectedValue, _expectedValue); 62 _ok = true; 63 } 64 65 /// Construct from unexpected value `unexpectedValue.` 66 this(Unexpected!E unexpectedValue) @trusted 67 { 68 /+ TODO: reuse opAssign? +/ 69 moveEmplace(unexpectedValue, _unexpectedValue); 70 _ok = false; 71 } 72 73 /// Assign from expected value `expectedValue.` 74 void opAssign()(auto ref T expectedValue) @trusted 75 { 76 /+ TODO: is this ok?: +/ 77 clear(); 78 moveEmplace(expectedValue, _expectedValue); 79 _ok = true; 80 } 81 82 /// Assign from unexpected value `unexpectedValue.` 83 void opAssign(E unexpectedValue) @trusted 84 { 85 clear(); 86 moveEmplace(unexpectedValue, _unexpectedValue); 87 _ok = false; 88 } 89 90 /// Clear (empty) contents. 91 private void clear() @trusted 92 { 93 release(); 94 static if (isAddress!T) 95 _expectedValue = null; 96 } 97 98 /// Release any memory used to store contents. 99 private void release() @trusted 100 { 101 import core.internal.traits : hasElaborateDestructor; 102 if (hasExpectedValue) 103 { 104 static if (!is(T == class)) 105 static if (hasElaborateDestructor!T) 106 destroy(_expectedValue); 107 _ok = false; 108 } 109 else 110 { 111 static if (!is(E == class)) 112 static if (hasElaborateDestructor!E) 113 destroy(_unexpectedValue); 114 destroy(_unexpectedValue); 115 /+ TODO: change _ok? +/ 116 } 117 } 118 119 /** Is `true` iff this has a expectedValue of type `T`. */ 120 bool hasExpectedValue() const => _ok; 121 122 import std.traits : CommonType; 123 124 /** Get current value if any or call function `elseWorkFun` with compatible return value. 125 * 126 * TODO: is this anywhere near what we want? 127 */ 128 CommonType!(T, typeof(elseWorkFun())) valueOr(alias elseWorkFun)() const 129 if (is(CommonType!(T, typeof(elseWorkFun())))) 130 => hasExpectedValue ? expectedValue : elseWorkFun(); /+ TODO: is this correct +/ 131 132 import std.functional : unaryFun; 133 134 /** If `this` is an expected value (of type `T`) apply `fun` on it and 135 * return result, otherwise return current unexpected value (of type `E`). 136 * 137 * See_Also: https://dlang.org/phobos/std_typecons.html#.apply 138 */ 139 Expected!(typeof(unaryFun!fun(T.init)), E) apply(alias fun)() @trusted 140 => hasExpectedValue ? 141 typeof(return)(unaryFun!fun(_expectedValue)) : 142 typeof(return)(Unexpected!E(_unexpectedValue)); 143 144 bool opEquals(const scope typeof(this) rhs) const @trusted 145 => _ok == rhs._ok && 146 (_ok ? 147 _expectedValue == rhs._expectedValue : 148 _unexpectedValue == rhs._unexpectedValue); 149 150 // range interface: 151 152 /// Check if empty. 153 bool empty() const @property => !_ok; 154 155 /// Get current value. 156 @property inout(T) front() inout @trusted 157 { 158 assert(_ok); 159 return _expectedValue; 160 } 161 162 /// Pop (clear) current value. 163 void popFront() 164 { 165 assert(_ok); 166 clear(); 167 } 168 169 private: 170 union 171 { 172 T _expectedValue; /+ TODO: do we need to default-initialize this somehow? +/ 173 Unexpected!E _unexpectedValue; 174 } 175 176 /** Is true if `_expectedValue` is defined, otherwise `_unexpectedValue` is 177 * defined. 178 * 179 * According to @andralex its ok to be opportunistic and default to 180 * `T.init`, because of the naming `Expected`. 181 */ 182 bool _ok = true; 183 } 184 185 /// Instantiator for `Expected` from an expected value `expectedValue.` 186 auto expected(T, E)(auto ref T expectedValue) 187 => Expected!(T, E)(expectedValue); 188 189 /// Instantiator for `Expected` from an unexpected value `unexpectedValue.` 190 auto unexpected(T, E)(auto ref E unexpectedValue) 191 => Expected!(T, E)(Unexpected!E(unexpectedValue)); 192 193 /// 194 pure nothrow @safe @nogc unittest { 195 alias T = string; // expected type 196 alias E = byte; 197 198 alias Esi = Expected!(T, byte); 199 200 // equality checks 201 assert(Esi("abc") == Esi("abc")); 202 assert(Esi("abcabc"[0 .. 3]) == 203 Esi("abcabc"[3 .. 6])); 204 205 auto x = Esi("abc"); 206 assert(x.hasExpectedValue); 207 assert(!x.empty); 208 assert(x.apply!(threeUnderscores) == Esi("___")); 209 210 x.popFront(); 211 assert(!x.hasExpectedValue); 212 assert(x.empty); 213 214 auto y = unexpected!(T, byte)(byte.init); 215 assert(!y.hasExpectedValue); 216 assert(x.empty); 217 assert(y.apply!(threeUnderscores) == Esi(Unexpected!byte(byte.init))); 218 } 219 220 version (unittest) 221 inout(string) threeUnderscores(inout(string) x) pure nothrow @safe @nogc 222 => "___";