1 module mage.util.option;
2 import mage;
3 
4 struct Option(T) {
5   alias WrappedType = T;
6   alias WrapperType = VariantN!(T.sizeof, T);
7 
8   /// The underlying value wrapped in a `std.VariantN`.
9   WrapperType value;
10 
11   this(U)(auto ref U u) {
12     this.value = cast(WrappedType)u;
13   }
14 
15   /// Whether this option has some value.
16   bool isSome() inout { return this.value.hasValue(); }
17 
18   /// Whether this option does not have some value.
19   bool isNone() inout { return !this.isSome(); }
20 
21   /// Clear the option so it no longer has a value.
22   void clear() { this.value = WrapperType(); }
23 
24   /// Implicit conversion to bool. Equivalent to isSome().
25   bool opCast(CastTargetType : bool)() inout { return this.isSome(); }
26 
27   /// Return the wrapped value. Throws an exception if there is no value.
28   auto ref inout(WrappedType) unwrap(string msg = null) inout {
29     return *this.value.peek!(WrappedType).enforce(msg ? msg : "This option has nothing to unwrap.");
30   }
31 
32   void opAssign(U : WrappedType)(auto ref U u) {
33     this.value = cast(WrappedType)u;
34   }
35 }
36 
37 
38 unittest
39 {
40   import std.exception;
41 
42   Option!int opt;
43   assert(opt.isNone);
44   assert(!opt.isSome);
45   assert(!opt);
46   assertThrown(opt.unwrap());
47   opt = 42;
48   assert(!opt.isNone);
49   assert(opt.isSome);
50   assert(opt);
51   assert(opt.unwrap() == 42);
52   opt.clear();
53   assert(opt.isNone);
54 }
55 
56 string toString(T)(in ref Option!T opt) {
57   if(opt.isSome) {
58     return "Option!%s(%s)".format(T.stringof, opt.unwrap());
59   }
60   return "Option!%s()".format(T.stringof);
61 }
62 
63 unittest
64 {
65   Option!int opt;
66   assert(opt.toString() == "Option!int()");
67   opt = 42;
68   assert(opt.toString() == "Option!int(42)");
69 }