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 @safe pure nothrow @nogc unittest
195 {
196     alias T = string;           // expected type
197     alias E = byte;
198 
199     alias Esi = Expected!(T, byte);
200 
201     // equality checks
202     assert(Esi("abc") == Esi("abc"));
203     assert(Esi("abcabc"[0 .. 3]) ==
204            Esi("abcabc"[3 .. 6]));
205 
206     auto x = Esi("abc");
207     assert(x.hasExpectedValue);
208     assert(!x.empty);
209     assert(x.apply!(threeUnderscores) == Esi("___"));
210 
211     x.popFront();
212     assert(!x.hasExpectedValue);
213     assert(x.empty);
214 
215     auto y = unexpected!(T, byte)(byte.init);
216     assert(!y.hasExpectedValue);
217     assert(x.empty);
218     assert(y.apply!(threeUnderscores) == Esi(Unexpected!byte(byte.init)));
219 }
220 
221 version(unittest)
222 inout(string) threeUnderscores(inout(string) x) @safe pure nothrow @nogc
223 	=> "___";