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 }