1 /// A version of `std.typecons.Nullable` that reliably works with immutable data types.
2 module rebindable.Nullable;
3 
4 import rebindable.Rebindable;
5 import std.traits;
6 
7 /// A version of `std.typecons.Nullable` that reliably works with immutable data types.
8 struct Nullable(T)
9 {
10     private bool isNull_ = true;
11 
12     private Rebindable!T payload;
13 
14     /// Construct a new `Nullable!T`.
15     public this(T value)
16     {
17         this.isNull_ = false;
18         this.payload.set(value);
19     }
20 
21     /// Return true if the `Nullable!T` does not contain a value.
22     public bool isNull() const nothrow pure @safe
23     {
24         return this.isNull_;
25     }
26 
27     /**
28      * Get the value stored previously.
29      * It is undefined to call this if no value is stored.
30      */
31     public CopyConstness!(This, T) get(this This)()
32     {
33         assert(!isNull, "Attempted Nullable.get of empty Nullable");
34         return payload.get;
35     }
36 
37     /**
38      * Get the value stored previously.
39      * If no value is stored, `default` is returned.
40      */
41     public CopyConstness!(This, T) get(this This)(T default_)
42     {
43         if (isNull)
44         {
45             return default_;
46         }
47         return payload.get;
48     }
49 
50     /// Assign a new value.
51     public void opAssign(T value)
52     {
53         nullify;
54         payload.set(value);
55         this.isNull_ = false;
56     }
57 
58     ///
59     public void opAssign(Nullable!T source)
60     {
61         if (source.isNull)
62         {
63             nullify;
64         }
65         else
66         {
67             this = source.payload.get;
68         }
69     }
70 
71     /// If a value is stored, destroy it.
72     public void nullify()
73     {
74         if (!this.isNull_)
75         {
76             this.payload.destroy;
77             this.isNull_ = true;
78         }
79     }
80 
81     ///
82     public bool opEquals(const Nullable other) const pure @safe
83     {
84         if (this.isNull != other.isNull)
85         {
86             return false;
87         }
88         if (this.isNull)
89         {
90             return true;
91         }
92         else
93         {
94             return payload.get == other.payload.get;
95         }
96     }
97 }
98 
99 ///
100 @nogc pure @safe unittest
101 {
102     Nullable!(const int) ni;
103 
104     assert(ni.isNull);
105 
106     ni = 5;
107     assert(!ni.isNull);
108     assert(ni.get == 5);
109 
110     ni.nullify;
111     assert(ni.isNull);
112 
113     assert(ni == Nullable!(const int)());
114 }
115 
116 @nogc pure @safe unittest
117 {
118     import rebindable.ProblematicType : ProblematicType;
119 
120     Nullable!int a;
121     a = 3;
122     assert(a.get == 3);
123 
124     Nullable!(const int) b;
125     assert(b.get(3) == 3);
126     b = 7;
127     assert(b.get == 7);
128     assert(b.get(3) == 7);
129 
130     int refs;
131     int* refsPtr = () @trusted { return &refs; }();
132     {
133         // construct
134         auto c = Nullable!ProblematicType(ProblematicType(refsPtr));
135         assert(refs == 1);
136 
137         // reassign
138         c = ProblematicType(refsPtr);
139         assert(refs == 1);
140         assert(c.get.properlyInitialized);
141 
142         // release
143         c.nullify;
144         assert(refs == 0);
145     }
146     assert(refs == 0);
147 }