diff --git a/std/typecons.d b/std/typecons.d index e67b706fdfb..5c99f994d85 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -1680,6 +1680,9 @@ struct Nullable(T) /** Constructor initializing $(D this) with $(D value). + +Params: + value = The value to initialize this `Nullable` with. */ this(inout T value) inout { @@ -1705,12 +1708,25 @@ Constructor initializing $(D this) with $(D value). } /** -Returns $(D true) if and only if $(D this) is in the null state. +Check if `this` is in the null state. + +Returns: + true $(B iff) `this` is in the null state, otherwise false. */ @property bool isNull() const @safe pure nothrow { return _isNull; } + +/// +unittest +{ + Nullable!int ni; + assert(ni.isNull); + + ni = 0; + assert(!ni.isNull); +} /** Forces $(D this) to the null state. @@ -1720,10 +1736,23 @@ Forces $(D this) to the null state. .destroy(_value); _isNull = true; } + +/// +unittest +{ + Nullable!int ni = 0; + assert(!ni.isNull); + + ni.nullify(); + assert(ni.isNull); +} /** Assigns $(D value) to the internally-held state. If the assignment succeeds, $(D this) becomes non-null. + +Params: + value = A value of type `T` to assign to this `Nullable`. */ void opAssign()(T value) { @@ -1731,9 +1760,37 @@ succeeds, $(D this) becomes non-null. _isNull = false; } +/** + If this `Nullable` wraps a type that already has a null value + (such as a pointer), then assigning the null value to this + `Nullable` is no different than assigning any other value of + type `T`, and the resulting code will look very strange. It + is strongly recommended that this be avoided by instead using + the version of `Nullable` that takes an additional `nullValue` + template argument. + */ +unittest +{ + //Passes + Nullable!(int*) npi; + assert(npi.isNull); + + //Passes?! + npi = null; + assert(!npi.isNull); +} + /** Gets the value. $(D this) must not be in the null state. This function is also called for the implicit conversion to $(D T). + +Throws: + A $(CXREF exception, AssertError) in the case where `isNull` + is true for this `Nullable`. This error will only be thrown in + non-release mode. + +Returns: + The value held internally by this `Nullable`. */ @property ref inout(T) get() inout @safe pure nothrow { @@ -1741,10 +1798,27 @@ This function is also called for the implicit conversion to $(D T). assert(!isNull, message); return _value; } + +/// +unittest +{ + import std.exception: assertThrown, assertNotThrown; + import core.exception: AssertError; + + Nullable!int ni; + //`get` is implicitly called. Will throw + //an AssertError in non-release mode + assertThrown!AssertError(ni == 0); + + ni = 0; + assertNotThrown!AssertError(ni == 0); +} /** Implicitly converts to $(D T). -$(D this) must not be in the null state. +$(D this) must not be in the null state +or a $(CXREF exception, AssertError) +will be thrown in non-release mode. */ alias get this; } @@ -1752,11 +1826,33 @@ $(D this) must not be in the null state. /// unittest { - Nullable!int a; - assert(a.isNull); - a = 5; - assert(!a.isNull); - assert(a == 5); + struct CustomerRecord + { + string name; + string address; + int customerNum; + } + + Nullable!CustomerRecord getByName(string name) + { + //A bunch of hairy stuff + + return Nullable!CustomerRecord.init; + } + + auto queryResult = getByName("Doe, John"); + if (!queryResult.isNull) + { + //Process Mr. Doe's customer record + auto address = queryResult.address; + auto customerNum = queryResult.customerNum; + + //Do some things with this customer's info + } + else + { + //Add the customer to the database + } } unittest @@ -2037,6 +2133,12 @@ particular value. For example, $(D Nullable!(uint, uint.max)) is an $(D uint) that sets aside the value $(D uint.max) to denote a null state. $(D Nullable!(T, nullValue)) is more storage-efficient than $(D Nullable!T) because it does not need to store an extra $(D bool). + +Params: + T = The wrapped type for which Nullable provides a null value. + + nullValue = The null value which denotes the null state of this + `Nullable`. Must be implicitly convertible to `T`. */ struct Nullable(T, T nullValue) { @@ -2044,6 +2146,9 @@ struct Nullable(T, T nullValue) /** Constructor initializing $(D this) with $(D value). + +Params: + value = The value to initialize this `Nullable` with. */ this(T value) { @@ -2068,7 +2173,10 @@ Constructor initializing $(D this) with $(D value). } /** -Returns $(D true) if and only if $(D this) is in the null state. +Check if `this` is in the null state. + +Returns: + true $(B iff) `this` is in the null state, otherwise false. */ @property bool isNull() const { @@ -2083,6 +2191,17 @@ Returns $(D true) if and only if $(D this) is in the null state. return _value == nullValue; } } + +/// +unittest +{ + Nullable!(int, -1) ni; + //Initialized to "null" state + assert(ni.isNull); + + ni = 0; + assert(!ni.isNull); +} /** Forces $(D this) to the null state. @@ -2091,19 +2210,64 @@ Forces $(D this) to the null state. { _value = nullValue; } + +/// +unittest +{ + Nullable!(int, -1) ni = 0; + assert(!ni.isNull); + + ni = -1; + assert(ni.isNull); +} /** -Assigns $(D value) to the internally-held state. No null checks are -made. Note that the assignment may leave $(D this) in the null state. +Assigns $(D value) to the internally-held state. If the assignment +succeeds, $(D this) becomes non-null. No null checks are made. Note +that the assignment may leave $(D this) in the null state. + +Params: + value = A value of type `T` to assign to this `Nullable`. + If it is `nullvalue`, then the internal state of + this `Nullable` will be set to null. */ void opAssign()(T value) { _value = value; } + +/** + If this `Nullable` wraps a type that already has a null value + (such as a pointer), and that null value is not given for + `nullValue`, then assigning the null value to this `Nullable` + is no different than assigning any other value of type `T`, + and the resulting code will look very strange. It is strongly + recommended that this be avoided by using `T`'s "built in" + null value for `nullValue`. + */ +unittest +{ + //Passes + enum nullVal = cast(int*)0xCAFEBABE; + Nullable!(int*, nullVal) npi; + assert(npi.isNull); + + //Passes?! + npi = null; + assert(!npi.isNull); +} /** Gets the value. $(D this) must not be in the null state. This function is also called for the implicit conversion to $(D T). + +Throws: + A $(CXREF exception, AssertError) in the case where `isNull` + is true for this `Nullable`. This error will only be thrown in + non-release mode. + +Returns: + The value held internally by this `Nullable`. */ @property ref inout(T) get() inout { @@ -2113,14 +2277,61 @@ This function is also called for the implicit conversion to $(D T). assert(!isNull, message); return _value; } + +/// +unittest +{ + import std.exception: assertThrown, assertNotThrown; + import core.exception: AssertError; + + Nullable!(int, -1) ni; + //`get` is implicitly called. Will throw + //an AssertError in non-release mode + assertThrown!AssertError(ni == 0); + + ni = 0; + assertNotThrown!AssertError(ni == 0); +} /** Implicitly converts to $(D T). -Gets the value. $(D this) must not be in the null state. +$(D this) must not be in the null state +or a $(CXREF exception, AssertError) +will be thrown in non-release mode. */ alias get this; } +/// +unittest +{ + Nullable!(size_t, size_t.max) indexOf(string[] haystack, string needle) + { + //Find the needle, returning -1 if not found + + return Nullable!(size_t, size_t.max).init; + } + + void sendLunchInvite(string name) + { + } + + //It's safer than C... + auto coworkers = ["Jane", "Jim", "Marry", "Fred"]; + auto pos = indexOf(coworkers, "Bob"); + if (!pos.isNull) + { + //Send Bob an invitation to lunch + sendLunchInvite(coworkers[pos]); + } + else + { + //Bob not found; report the error + } + + //And there's no overhead + static assert(Nullable!(size_t, size_t.max).sizeof == size_t.sizeof); +} unittest { import std.exception : assertThrown;