Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 139 additions & 28 deletions std/typecons.d
Original file line number Diff line number Diff line change
Expand Up @@ -2569,25 +2569,13 @@ Practically `Nullable!T` stores a `T` and a `bool`.
*/
struct Nullable(T)
{
// simple case: type is freely constructable
static if (__traits(compiles, { T _value; }))
private union DontCallDestructorT
{
private T _value;
}
// type is not constructable, but also has no way to notice
// that we're assigning to an uninitialized variable.
else static if (!hasElaborateAssign!T)
{
private T _value = T.init;
}
else
{
static assert(false,
"Cannot construct " ~ typeof(this).stringof ~
": type has no default constructor and overloaded assignment."
);
T payload;
}

private DontCallDestructorT _value = DontCallDestructorT.init;

private bool _isNull = true;

/**
Expand All @@ -2598,10 +2586,21 @@ Params:
*/
this(inout T value) inout
{
_value = value;
_value.payload = value;
_isNull = false;
}

static if (is(T == struct) && hasElaborateDestructor!T)
{
~this()
{
if (!_isNull)
{
destroy(_value.payload);
}
}
}

/**
If they are both null, then they are equal. If one is null and the other
is not, then they are not equal. If they are both non-null, then they are
Expand All @@ -2613,14 +2612,14 @@ Params:
return rhs._isNull;
if (rhs._isNull)
return false;
return _value == rhs._value;
return _value.payload == rhs._value.payload;
}

/// Ditto
bool opEquals(U)(auto ref const(U) rhs) const
if (is(typeof(this.get == rhs)))
{
return _isNull ? false : rhs == _value;
return _isNull ? false : rhs == _value.payload;
}

///
Expand Down Expand Up @@ -2706,7 +2705,7 @@ Params:
if (isNull)
put(writer, "Nullable.null");
else
formatValue(writer, _value, fmt);
formatValue(writer, _value.payload, fmt);
}

//@@@DEPRECATED_2.086@@@
Expand All @@ -2719,7 +2718,7 @@ Params:
}
else
{
sink.formatValue(_value, fmt);
sink.formatValue(_value.payload, fmt);
}
}

Expand All @@ -2734,7 +2733,7 @@ Params:
}
else
{
sink.formatValue(_value, fmt);
sink.formatValue(_value.payload, fmt);
}
}

Expand Down Expand Up @@ -2777,9 +2776,9 @@ Forces `this` to the null state.
void nullify()()
{
static if (is(T == class) || is(T == interface))
_value = null;
_value.payload = null;
else
.destroy(_value);
.destroy(_value.payload);
_isNull = true;
}

Expand All @@ -2802,7 +2801,21 @@ Params:
*/
void opAssign()(T value)
{
_value = value;
import std.algorithm.mutation : moveEmplace, move;

// the lifetime of the value in copy shall be managed by
// this Nullable, so we must avoid calling its destructor.
auto copy = DontCallDestructorT(value);

if (_isNull)
{
// trusted since payload is known to be T.init here.
() @trusted { moveEmplace(copy.payload, _value.payload); }();
}
else
{
move(copy.payload, _value.payload);
}
_isNull = false;
}

Expand Down Expand Up @@ -2842,13 +2855,13 @@ Returns:
{
enum message = "Called `get' on null Nullable!" ~ T.stringof ~ ".";
assert(!isNull, message);
return _value;
return _value.payload;
}

/// ditto
@property get(U)(inout(U) fallback) inout @safe pure nothrow
{
return isNull ? fallback : _value;
return isNull ? fallback : _value.payload;
}

///
Expand Down Expand Up @@ -3274,6 +3287,102 @@ auto nullable(T)(T t)

assert(foo.approxEqual(bar));
}
// bugzilla issue 19037
@safe unittest
{
import std.datetime : SysTime;

struct Test
{
bool b;

nothrow invariant { assert(b == true); }

SysTime _st;

static bool destroyed;

@disable this();
this(bool b) { this.b = b; }
~this() @safe { destroyed = true; }

// mustn't call opAssign on Test.init in Nullable!Test, because the invariant
// will be called before opAssign on the Test.init that is in Nullable
// and Test.init violates its invariant.
void opAssign(Test rhs) @safe { assert(false); }
}

{
Nullable!Test nt;

nt = Test(true);

// destroy value
Test.destroyed = false;

nt.nullify;

assert(Test.destroyed);

Test.destroyed = false;
}
// don't run destructor on T.init in Nullable on scope exit!
assert(!Test.destroyed);
}
// check that the contained type's destructor is called on assignment
@system unittest
{
struct S
{
// can't be static, since we need a specific value's pointer
bool* destroyedRef;

~this()
{
if (this.destroyedRef)
{
*this.destroyedRef = true;
}
}
}

Nullable!S ns;

bool destroyed;

ns = S(&destroyed);

// reset from rvalue destruction in Nullable's opAssign
destroyed = false;

// overwrite Nullable
ns = S(null);

// the original S should be destroyed.
assert(destroyed == true);
}
// check that the contained type's destructor is still called when required
@system unittest
{
bool destructorCalled = false;

struct S
{
bool* destroyed;
~this() { *this.destroyed = true; }
}

{
Nullable!S ns;
}
assert(!destructorCalled);
{
Nullable!S ns = Nullable!S(S(&destructorCalled));

destructorCalled = false; // reset after S was destroyed in the NS constructor
}
assert(destructorCalled);
}

/**
Just like `Nullable!T`, except that the null state is defined as a
Expand Down Expand Up @@ -3406,7 +3515,9 @@ Params:
*/
void opAssign()(T value)
{
_value = value;
import std.algorithm.mutation : swap;

swap(value, _value);
}

/**
Expand Down