1 /++
2 Error handling that bundles exceptions with return values.
3 
4 The design of this module is based on C++'s proposed
5 [std::expected](https://wg21.link/p0323) and Rust's
6 [std::result](https://doc.rust-lang.org/std/result/). See
7 ["Expect the Expected"](https://www.youtube.com/watch?v=nVzgkepAg5Y) by
8 Andrei Alexandrescu for further background.
9 
10 License: MIT
11 Author: Paul Backus
12 +/
13 module expectations;
14 
15 /// $(H3 Basic Usage)
16 @safe unittest {
17     import std.exception: assertThrown;
18 
19     Expected!int charToDigit(char c)
20     {
21         int d = c - '0';
22         if (d >= 0 && d < 10) {
23             return expected(d);
24         } else {
25             return missing!int(
26                 new Exception(c ~ " is not a valid digit")
27             );
28         }
29     }
30 
31     auto goodResult = charToDigit('7');
32     auto badResult = charToDigit('&');
33 
34     assert(goodResult.hasValue);
35     assert(goodResult.value == 7);
36 
37     assert(!badResult.hasValue);
38     assertThrown(badResult.value);
39     assert(badResult.error.msg == "& is not a valid digit");
40 }
41 
42 /**
43  * An exception that represents an error value.
44  */
45 class Unexpected(T) : Exception
46 {
47 	/**
48 	 * The error value.
49 	 */
50 	T value;
51 
52 	/**
53 	 * Constructs an `Unexpected` exception from a value.
54 	 */
55 	pure @safe @nogc nothrow
56 	this(T value, string file = __FILE__, size_t line = __LINE__)
57 	{
58 		super("unexpected value", file, line);
59 		this.value = value;
60 	}
61 }
62 
63 /**
64  * An `Expected!(T, E)` contains either an expected value of type `T`, or an
65  * error value of type `E` explaining why the expected value couldn't be
66  * produced.
67  *
68  * The default type for the error value is `Exception`.
69  *
70  * A function that returns an `Expected` object has the following advantages
71  * over one that may throw an exception or return an error code:
72  *
73  * $(LIST
74  *   * It leaves the choice between manual error checking and automatic stack
75  *     unwinding up to the caller.
76  *   * It allows error handling to be deferred until the return value is
77  *     actually needed (if ever).
78  *   * It can be easily composed with other functions using [map] and
79  *     [flatMap], which propagates error values automatically.
80  *   * It can be used in `nothrow` code.
81  * )
82  *
83  * An `Expected!(T, E)` is initialized by default to contain the value `T.init`.
84  *
85  * $(PITFALL Unlike a thrown exception, an error returned via an `Expected`
86  * object will be silently ignored if the function's return value is discarded.
87  * For best results, functions that return `Expected` objects should be marked
88  * as `pure`, so that the D compiler will warn about discarded return values.)
89  */
90 struct Expected(T, E = Exception)
91 	if (!is(T == E) && !is(T == void))
92 {
93 private:
94 
95 	import sumtype;
96 
97 	SumType!(T, E) data;
98 
99 public:
100 
101 	/**
102 	 * Constructs an `Expected` object that contains an expected value.
103 	 */
104 	this(T value)
105 	{
106 		data = value;
107 	}
108 
109 	/**
110 	 * Constructs an `Expected` object that contains an error value.
111 	 */
112 	this(E err)
113 	{
114 		data = err;
115 	}
116 
117 	/**
118 	 * Assigns an expected value to an `Expected` object.
119 	 */
120 	void opAssign(T value)
121 	{
122 		data = value;
123 	}
124 
125 	/**
126 	 * Assigns an error value to an `Expected` object.
127 	 */
128 	void opAssign(E err)
129 	{
130 		data = err;
131 	}
132 
133 	/**
134 	 * Checks whether this `Expected` object contains a specific expected value.
135 	 */
136 	bool opEquals(T rhs)
137 	{
138 		return data.match!(
139 			(T value) => value == rhs,
140 			(E _) => false
141 		);
142 	}
143 
144 	/**
145 	 * Checks whether this `Expected` object contains a specific error value.
146 	 */
147 	bool opEquals(E rhs)
148 	{
149 		return data.match!(
150 			(T _) => false,
151 			(E err) => err == rhs
152 		);
153 	}
154 
155 	/**
156 	 * Checks whether this `Expected` object and `rhs` contain the same expected
157 	 * value or error value.
158 	 */
159 	bool opEquals(Expected!(T, E) rhs)
160 	{
161 		return data.match!(
162 			(T value) => rhs == value,
163 			(E err) => rhs == err
164 		);
165 	}
166 
167 	/**
168 	 * Checks whether this `Expected` object contains an expected value or an
169 	 * error value.
170 	 */
171 	bool hasValue() const
172 	{
173 		return data.match!(
174 			(const T _) => true,
175 			(const E _) => false
176 		);
177 	}
178 
179 	/**
180 	 * Returns the expected value if there is one. Otherwise, throws an
181 	 * exception
182 	 *
183 	 * Throws:
184 	 *   If `E` inherits from `Throwable`, the error value is thrown.
185 	 *   Otherwise, an [Unexpected] instance containing the error value is
186 	 *   thrown.
187 	 */
188 	inout(T) value() inout
189 	{
190 		return data.match!(
191 			(inout(T) value) => value,
192 			delegate T (inout(E) err) {
193 				static if(is(E : Throwable)) {
194 					throw error;
195 				} else {
196 					throw new Unexpected!E(error);
197 				}
198 			}
199 		);
200 	}
201 
202 	/**
203 	 * Returns the error value. May only be called when `hasValue` returns
204 	 * `false`.
205 	 */
206 	inout(E) error() inout
207 		in { assert(!hasValue); }
208 	do {
209 		import std.exception: assumeWontThrow;
210 
211 		return data.tryMatch!(
212 			(inout(E) err) => err
213 		).assumeWontThrow;
214 	}
215 
216 	deprecated("Renamed to `error`")
217 	inout(E) exception() inout
218 	{
219 		return error;
220 	}
221 
222 	/**
223 	 * Returns the expected value if present, or a default value otherwise.
224 	 */
225 	inout(T) valueOr(inout(T) defaultValue) inout
226 	{
227 		return data.match!(
228 			(inout(T) value) => value,
229 			(inout(E) _) => defaultValue
230 		);
231 	}
232 }
233 
234 // Construction
235 @safe nothrow unittest {
236 	assert(__traits(compiles, Expected!int(123)));
237 	assert(__traits(compiles, Expected!int(new Exception("oops"))));
238 }
239 
240 // Assignment
241 @safe nothrow unittest {
242 	Expected!int x;
243 
244 	assert(__traits(compiles, x = 123));
245 	assert(__traits(compiles, x = new Exception("oops")));
246 }
247 
248 // Self assignment
249 @safe nothrow unittest {
250 	Expected!int x, y;
251 
252 	assert(__traits(compiles, x = y));
253 }
254 
255 // Equality with self
256 @system unittest {
257 	int n = 123;
258 	Exception e = new Exception("oops");
259 
260 	Expected!int x = n;
261 	Expected!int y = n;
262 	Expected!int z = e;
263 	Expected!int w = e;
264 
265 	assert(x == y);
266 	assert(z == w);
267 	assert(x != z);
268 	assert(z != x);
269 }
270 
271 // Equality with T and Exception
272 @system unittest {
273 	int n = 123;
274 	Exception e = new Exception("oops");
275 
276 	Expected!int x = n;
277 	Expected!int y = e;
278 
279 	() @safe {
280 		assert(x == n);
281 		assert(y != n);
282 		assert(x != 456);
283 	}();
284 
285 	assert(x != e);
286 	assert(y == e);
287 	assert(y != new Exception("oh no"));
288 }
289 
290 // hasValue
291 @safe nothrow unittest {
292 	Expected!int x = 123;
293 	Expected!int y = new Exception("oops");
294 
295 	assert(x.hasValue);
296 	assert(!y.hasValue);
297 }
298 
299 // value
300 @safe unittest {
301 	import std.exception: collectException;
302 
303 	Expected!int x = 123;
304 	Expected!int y = new Exception("oops");
305 
306 	assert(x.value == 123);
307 	assert(collectException(y.value).msg == "oops");
308 }
309 
310 // error
311 @system unittest {
312 	Exception e = new Exception("oops");
313 	Expected!int x = e;
314 
315 	assert(x.error == e);
316 }
317 
318 // valueOr
319 @safe nothrow unittest {
320 	Expected!int x = 123;
321 	Expected!int y = new Exception("oops");
322 
323 	assert(x.valueOr(456) == 123);
324 	assert(y.valueOr(456) == 456);
325 }
326 
327 // const(Expected)
328 @safe unittest {
329 	const(Expected!int) x = 123;
330 	const(Expected!int) y = new Exception("oops");
331 
332 	// hasValue
333 	assert(x.hasValue);
334 	assert(!y.hasValue);
335 	// value
336 	assert(x.value == 123);
337 	// error
338 	assert(y.error.msg == "oops");
339 	// valueOr
340 	assert(x.valueOr(456) == 123);
341 	assert(y.valueOr(456) == 456);
342 }
343 
344 // Explicit error type
345 @safe unittest {
346 	import std.exception: assertThrown;
347 
348 	Expected!(int, string) x = 123;
349 	Expected!(int, string) y = "oops";
350 
351 	// haValue
352 	assert(x.hasValue);
353 	assert(!y.hasValue);
354 	// value
355 	assert(x.value == 123);
356 	assertThrown!(Unexpected!string)(y.value);
357 	// error
358 	assert(y.error == "oops");
359 	// valueOr
360 	assert(x.valueOr(456) == 123);
361 	assert(y.valueOr(456) == 456);
362 }
363 
364 /**
365  * Creates an `Expected` object from an expected value, with type inference.
366  */
367 Expected!(T, E) expected(T, E = Exception)(T value)
368 {
369 	return Expected!(T, E)(value);
370 }
371 
372 // Default error type
373 @safe nothrow unittest {
374 	assert(__traits(compiles, expected(123)));
375 	assert(is(typeof(expected(123)) == Expected!int));
376 }
377 
378 // Explicit error type
379 @safe nothrow unittest {
380 	assert(__traits(compiles, expected!(int, string)(123)));
381 	assert(is(typeof(expected!(int, string)(123)) == Expected!(int, string)));
382 }
383 
384 /**
385  * Creates an `Expected` object from an error value.
386  */
387 Expected!(T, E) missing(T, E)(E err)
388 {
389 	return Expected!(T, E)(err);
390 }
391 
392 @safe nothrow unittest {
393 	Exception e = new Exception("oops");
394 	assert(__traits(compiles, missing!int(e)));
395 	assert(is(typeof(missing!int(e)) == Expected!int));
396 }
397 
398 @safe nothrow unittest {
399 	auto x = missing!int("oops");
400 	assert(__traits(compiles, missing!int("oops")));
401 	assert(is(typeof(missing!int("oops")) == Expected!(int, string)));
402 }
403 
404 deprecated("Renamed to `missing`")
405 Expected!(T, E) unexpected(T, E)(E err)
406 {
407 	return missing!T(err);
408 }
409 
410 /**
411  * Applies a function to the expected value in an `Expected` object.
412  *
413  * If no expected value is present, the original error value is passed through
414  * unchanged, and the function is not called.
415  *
416  * Returns:
417  *   A new `Expected` object containing the result.
418  */
419 template map(alias fun)
420 {
421 	/**
422 	 * The actual `map` function.
423 	 *
424 	 * Params:
425 	 *   self = an [Expected] object.
426 	 */
427 	auto map(T, E)(Expected!(T, E) self)
428 		if (is(typeof(fun(self.value))))
429 	{
430 		import sumtype: match;
431 
432 		alias U = typeof(fun(self.value));
433 
434 		return self.data.match!(
435 			(T value) => expected!(U, E)(fun(value)),
436 			(E err) => missing!U(err)
437 		);
438 	}
439 }
440 
441 @safe unittest {
442 	import std.math: approxEqual;
443 
444 	Expected!int x = 123;
445 	Expected!int y = new Exception("oops");
446 
447 	double half(int n) { return n / 2.0; }
448 
449 	assert(__traits(compiles, () nothrow {
450 		x.map!half;
451 	}));
452 
453 	assert(x.map!half.value.approxEqual(61.5));
454 	assert(y.map!half.error.msg == "oops");
455 
456 	alias mapHalf = map!half;
457 
458 	assert(mapHalf(Expected!int(123)).value.approxEqual(61.5));
459 }
460 
461 @safe unittest {
462 	Expected!(int, string) x = 123;
463 	Expected!(int, string) y = "oops";
464 
465 	assert(x.map!(n => n*2).value == 246);
466 	assert(y.map!(n => n*2).error == "oops");
467 }
468 
469 /**
470  * Forwards the expected value in an `Expected` object to a function that
471  * returns an `Expected` result.
472  *
473  * If the original `Expected` object contains an error value, it is passed
474  * through to the result, and the function is not called.
475  *
476  * Returns:
477  *   The `Expected` object returned from the function, or an `Expected` object
478  *   of the same type containing the original error value.
479  */
480 template flatMap(alias fun)
481 {
482 	/**
483 	 * The actual `flatMap` function.
484 	 *
485 	 * Params:
486 	 *   self = an [Expected] object
487 	 */
488 	auto flatMap(T, E1)(Expected!(T, E1) self)
489 		if (is(typeof(fun(self.value)) == Expected!(U, E2), U, E2)
490 		    && is(E1 : E2))
491 	{
492 		import sumtype: match;
493 
494 		alias ExpectedUE2 = typeof(fun(self.value));
495 		alias E2 = typeof(ExpectedUE2.init.error());
496 
497 		return self.data.match!(
498 			(T value) => fun(value),
499 			(E1 err) => ExpectedUE2(cast(E2) err)
500 		);
501 	}
502 }
503 
504 @safe unittest {
505 	import std.math: approxEqual;
506 
507 	Expected!int x = 123;
508 	Expected!int y = 0;
509 	Expected!int z = new Exception("oops");
510 
511 	Expected!double recip(int n)
512 	{
513 		if (n == 0) {
514 			return missing!double(new Exception("Division by zero"));
515 		} else {
516 			return expected(1.0 / n);
517 		}
518 	}
519 
520 	assert(__traits(compiles, () nothrow {
521 		x.flatMap!recip;
522 	}));
523 
524 	assert(x.flatMap!recip.value.approxEqual(1.0/123));
525 	assert(y.flatMap!recip.error.msg == "Division by zero");
526 	assert(z.flatMap!recip.error.msg == "oops");
527 
528 	alias flatMapRecip = flatMap!recip;
529 
530 	assert(flatMapRecip(Expected!int(123)).value.approxEqual(1.0/123));
531 }
532 
533 @safe unittest {
534 	import std.math: approxEqual;
535 
536 	Expected!(int, string) x = 123;
537 	Expected!(int, string) y = 0;
538 	Expected!(int, string) z = "oops";
539 
540 	Expected!(double, string) recip(int n)
541 	{
542 		if (n == 0) {
543 			return missing!double("Division by zero");
544 		} else {
545 			return expected!(double, string)(1.0 / n);
546 		}
547 	}
548 
549 	assert(__traits(compiles, () nothrow {
550 		x.flatMap!recip;
551 	}));
552 
553 	assert(x.flatMap!recip.value.approxEqual(1.0/123));
554 	assert(y.flatMap!recip.error == "Division by zero");
555 	assert(z.flatMap!recip.error == "oops");
556 }
557 
558 deprecated("Renamed to `flatMap`")
559 template andThen(alias fun)
560 {
561 	alias andThen = flatMap!fun;
562 }