1 /++ Result type. 2 +/ 3 module nxt.result; 4 5 @safe: 6 7 /++ Result of `T` with optional error `E`. 8 Designed for error handling where an operation can either succeed or fail. 9 - TODO: Add member `toRange` alias with `opSlice` 10 - TODO: Add member visit() 11 +/ 12 struct Result(T, E = void) { 13 private enum hasError = !is(E == void); 14 15 static if (!__traits(isPOD, T)) 16 import core.lifetime : move, moveEmplace; 17 18 this(T value) { 19 static if (__traits(isPOD, T)) 20 _value = value; 21 else 22 () @trusted { moveEmplace(value, _value); }(); /+ TODO: remove when compiler does this +/ 23 _isValue = true; 24 } 25 26 static if (hasError) { 27 this(E error) { 28 static if (__traits(isPOD, T)) 29 _error = error; 30 else 31 () @trusted { moveEmplace(error, _error); }(); /+ TODO: remove when compiler does this +/ 32 _isValue = false; 33 } 34 } 35 36 ~this() @trusted { 37 import core.internal.traits : hasElaborateDestructor; 38 if (isValue) { 39 static if (hasElaborateDestructor!T) 40 .destroy(_value); 41 } else { 42 static if (hasElaborateDestructor!E) 43 .destroy(_error); 44 } 45 } 46 47 ref typeof(this) opAssign(T value) @trusted { 48 static if (__traits(isPOD, T)) 49 _value = value; 50 else 51 () @trusted { move(value, _value); }(); /+ TODO: remove when compiler does this +/ 52 _isValue = true; 53 return this; 54 } 55 56 static if (hasError) { 57 ref typeof(this) opAssign(E error) @trusted { 58 static if (__traits(isPOD, E)) 59 _error = error; 60 else 61 () @trusted { move(error, _error); }(); /+ TODO: remove when compiler does this +/ 62 _isValue = false; 63 return this; 64 } 65 } 66 67 @property: 68 ref inout(T) value() inout scope @trusted return in(isValue) => _value; 69 static if (hasError) { 70 ref inout(E) error() inout scope @trusted return in(!isValue) => _error; 71 } 72 73 // ditto 74 ref inout(T) opUnary(string op)() inout scope return if (op == "*") => value; 75 76 bool opEquals(in T that) const scope => _isValue ? value == that : false; 77 bool opEquals(scope const ref T that) const scope => _isValue ? value == that : false; 78 bool opEquals(scope const ref typeof(this) that) const scope @trusted { 79 if (this.isValue && that.isValue) 80 return this._value == that._value; 81 return this.isValue == that.isValue; 82 } 83 84 string toString() const scope pure nothrow { 85 if (isValue) 86 return toValueString; 87 static if (hasError) 88 return toErrorString; 89 else 90 return "invalid"; 91 } 92 93 private string toValueString() const scope pure nothrow @trusted { 94 // TODO: remove when std.conv.to!string(_error) doens't throw for trivial types: 95 try { 96 import std.conv : to; 97 return _value.to!string; 98 } catch (Exception _) 99 return typeof(return).init; 100 } 101 102 static if (hasError) { 103 private string toErrorString() const scope pure nothrow @trusted { 104 // TODO: remove when std.conv.to!string(_error) doens't throw for trivial types: 105 try { 106 import std.conv : to; 107 return _error.to!string; 108 } catch (Exception _) 109 return typeof(return).init; 110 } 111 } 112 113 pure nothrow @nogc: 114 bool isValue() const scope => _isValue; 115 116 static if (hasError) { 117 bool isError() const scope => !_isValue; 118 } 119 120 bool opCast(T : bool)() const scope => _isValue; 121 122 static typeof(this) invalid() => typeof(this).init; 123 private: 124 /++ TODO: avoid `_isValue` when `T` is a pointer and `_error.sizeof` <= 125 `size_t.sizeof:` by making some part part of pointer the 126 discriminator for a defined value preferrably the lowest bit. 127 +/ 128 129 static if (hasError) { 130 union { 131 T _value; 132 E _error; 133 } 134 } else { 135 T _value; 136 } 137 138 bool _isValue; 139 } 140 141 /// to string conversion 142 @safe pure nothrow unittest { 143 alias T = int; 144 alias R = Result!T; 145 const R r1; 146 assert(r1.toString == "invalid"); 147 const R r2 = 42; 148 assert(r2.toString == "42"); 149 R r3 = r2; 150 r3 = 42; 151 assert(*r3 == 42); 152 assert(r3 == 42); 153 T v42 = 42; 154 assert(r3 == v42); 155 } 156 157 /// result of uncopyable type 158 @safe pure nothrow @nogc unittest { 159 alias T = Uncopyable; 160 alias R = Result!T; 161 const R r_ = R(T.init); 162 R r1; 163 assert(!r1); 164 assert(r1 != r_); 165 assert(!r1.isValue); 166 T t = T(42); 167 r1 = move(t); 168 assert(r1 != r_); 169 assert(*r1 == T(42)); 170 R r2 = T(43); 171 assert(*r2 == T(43)); 172 assert(r2.value == T(43)); 173 } 174 175 /// result of pointer and error enum 176 @safe pure nothrow unittest { 177 alias V = ulong; 178 alias T = V*; 179 enum E { first, second } 180 alias R = Result!(T, E); 181 R r1; 182 assert(!r1); 183 assert(r1 == R.invalid); 184 assert(r1 != R(T.init)); 185 assert(!r1.isValue); 186 assert(r1.isError); 187 assert(r1.error == E.init); 188 assert(r1.error == E.first); 189 T v = new V(42); 190 r1 = move(v); 191 assert(r1 != R(T.init)); 192 assert(**r1 == V(42)); 193 } 194 195 /// result of pointer and error enum toString 196 @safe pure nothrow unittest { 197 alias V = ulong; 198 alias T = V*; 199 enum E { first, second } 200 alias R = Result!(T, E); 201 R r1; 202 assert(r1.toString == "first"); 203 r1 = T.init; 204 assert(r1.toString == "null"); 205 } 206 207 /// result of pointer and error enum 208 @safe pure nothrow unittest { 209 alias V = ulong; 210 alias T = V*; 211 enum E { first, second } 212 alias R = Result!(T, E); 213 R r2 = new V(43); 214 assert(**r2 == V(43)); 215 assert(*r2.value == V(43)); 216 } 217 218 /// result of pointer and error enum 219 @safe pure nothrow unittest { 220 alias V = ulong; 221 alias T = V*; 222 enum E { first, second } 223 alias R = Result!(T, E); 224 R r2 = E.first; 225 assert(r2.error == E.first); 226 r2 = E.second; 227 assert(r2.error == E.second); 228 } 229 230 version (unittest) { 231 import core.lifetime : move; 232 private static struct Uncopyable { this(this) @disable; int _x; } 233 }