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 { 98 _expectedValue = null; 99 } 100 } 101 102 /// Release any memory used to store contents. 103 private void release() @trusted 104 { 105 import core.internal.traits : hasElaborateDestructor; 106 if (hasExpectedValue) 107 { 108 static if (!is(T == class)) 109 { 110 static if (hasElaborateDestructor!T) 111 { 112 destroy(_expectedValue); 113 } 114 } 115 _ok = false; 116 } 117 else 118 { 119 static if (!is(E == class)) 120 { 121 static if (hasElaborateDestructor!E) 122 { 123 destroy(_unexpectedValue); 124 } 125 } 126 destroy(_unexpectedValue); 127 // TODO change _ok? 128 } 129 } 130 131 /** Is `true` iff this has a expectedValue of type `T`. */ 132 bool hasExpectedValue() const 133 { 134 return _ok; 135 } 136 137 import std.traits : CommonType; 138 139 /** Get current value if any or call function `elseWorkFun` with compatible return value. 140 * 141 * TODO is this anywhere near what we want? 142 */ 143 CommonType!(T, typeof(elseWorkFun())) valueOr(alias elseWorkFun)() const 144 if (is(CommonType!(T, typeof(elseWorkFun())))) 145 { 146 if (hasExpectedValue) 147 { 148 return expectedValue; 149 } 150 else 151 { 152 return elseWorkFun(); // TODO is this correct 153 } 154 } 155 156 import std.functional : unaryFun; 157 158 /** If `this` is an expected value (of type `T`) apply `fun` on it and 159 * return result, otherwise return current unexpected value (of type `E`). 160 * 161 * See_Also: https://dlang.org/phobos/std_typecons.html#.apply 162 */ 163 Expected!(typeof(unaryFun!fun(T.init)), E) apply(alias fun)() @trusted 164 { 165 alias fn = unaryFun!fun; 166 if (hasExpectedValue) 167 { 168 return typeof(return)(fn(_expectedValue)); 169 } 170 else 171 { 172 return typeof(return)(Unexpected!E(_unexpectedValue)); 173 } 174 } 175 176 // range interface: 177 178 /// Check if empty. 179 @property bool empty() const 180 { 181 return !_ok; 182 } 183 184 /// Get current value. 185 @property inout(T) front() inout @trusted 186 { 187 assert(_ok); 188 return _expectedValue; 189 } 190 191 /// Pop (clear) current value. 192 void popFront() 193 { 194 assert(_ok); 195 clear(); 196 } 197 198 private: 199 union 200 { 201 T _expectedValue; // TODO do we need to default-initialize this somehow? 202 Unexpected!E _unexpectedValue; 203 } 204 205 /** Is true if `_expectedValue` is defined, otherwise `_unexpectedValue` is 206 * defined. 207 * 208 * According to @andralex its ok to be opportunistic and default to 209 * `T.init`, because of the naming `Expected`. 210 */ 211 bool _ok = true; 212 } 213 214 /// Insantiator for `Expected` from an expected value `expectedValue.` 215 auto expected(T, E)(auto ref T expectedValue) 216 { 217 return Expected!(T, E)(expectedValue); 218 } 219 220 /// Insantiator for `Expected` from an unexpected value `unexpectedValue.` 221 auto unexpected(T, E)(auto ref E unexpectedValue) 222 { 223 return Expected!(T, E)(Unexpected!E(unexpectedValue)); 224 } 225 226 /// 227 @safe pure nothrow @nogc unittest 228 { 229 alias T = string; // expected type 230 alias E = byte; 231 232 alias Esi = Expected!(T, byte); 233 234 // equality checks 235 assert(Esi("abc") == Esi("abc")); 236 assert(Esi("abcabc"[0 .. 3]) == 237 Esi("abcabc"[3 .. 6])); 238 239 auto x = Esi("abc"); 240 assert(x.hasExpectedValue); 241 assert(!x.empty); 242 assert(x.apply!(threeUnderscores) == Esi("___")); 243 244 x.popFront(); 245 assert(!x.hasExpectedValue); 246 assert(x.empty); 247 248 auto y = unexpected!(T, byte)(byte.init); 249 assert(!y.hasExpectedValue); 250 assert(x.empty); 251 assert(y.apply!(threeUnderscores) == Esi(Unexpected!byte(byte.init))); 252 } 253 254 version(unittest) 255 inout(string) threeUnderscores(inout(string) x) @safe pure nothrow @nogc 256 { 257 return "___"; 258 }