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