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