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