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