On Wednesday, 29 May 2013 at 04:15:52 UTC, Walter Bright wrote:
It's not in Phobos yet:
I did a pull request a while ago that stalled for some reason I
don't remember now, but the struct I did needed to be a little
more complex than yours to pass all the tests....
======
struct NotNull(T) if(__traits(compiles, { T t; assert(t is null);
}))
{
private T _notNullData;
@property inout(T) _notNullDataHelper() inout
{
assert(_notNullData !is null); // sanity check of
invariant
return _notNullData;
}
// Apparently a compiler bug - the invariant being
uncommented breaks all kinds of stuff.
// invariant() { assert(_notNullData !is null); }
alias _notNullDataHelper this; /// this is substitutable for
the regular (nullable) type
@disable this();
// this could arguably break the static type check because
// you can assign it from a variable that is null.. but I
// think it is important that NotNull!Object = new Object();
// works, without having to say assumeNotNull(new Object())
// for convenience of using with local variables.
/// constructs with a runtime not null check (via assert())
this(T value)
{
assert(value !is null);
_notNullData = value;
}
@disable this(typeof(null)); /// the null literal can be
caught at compile time
@disable typeof(this) opAssign(typeof(null)); /// ditto
/// .
NotNull!T opAssign(NotNull!T rhs)
{
this._notNullData = rhs._notNullData;
return this;
}
}
/// A convenience function to construct a NotNull value from
something you know isn't null.
NotNull!T assumeNotNull(T)(T t)
{
return NotNull!T(t); // note the constructor asserts it is
not null
}
/// A convenience function to check for null. If you pass null,
it will throw an exception. Otherwise, return NotNull!T.
NotNull!T enforceNotNull(T)(T t)
{
enforce(t !is null);
return NotNull!T(t);
}
unittest
{
import core.exception;
import std.exception;
void NotNullCompiliationTest1()() // I'm making these
templates to defer compiling them
{
NotNull!(int*) defaultInitiliation; // should fail
because this would be null otherwise
}
assert(!__traits(compiles, NotNullCompiliationTest1!()()));
void NotNullCompiliationTest2()()
{
NotNull!(int*) defaultInitiliation = null; // should fail
here too at compile time
}
assert(!__traits(compiles, NotNullCompiliationTest2!()()));
int dummy;
NotNull!(int*) foo = &dummy;
assert(!__traits(compiles, foo = null)); // again, literal
null is caught at compile time
int* test;
test = &dummy;
foo = assumeNotNull(test); // should be fine
void bar(int* a) {}
// these should both compile, since NotNull!T is a subtype of
T
bar(test);
bar(foo);
void takesNotNull(NotNull!(int*) a) { }
assert(!__traits(compiles, takesNotNull(test))); // should
not work; plain int might be null
takesNotNull(foo); // should be fine
takesNotNull(assumeNotNull(test)); // this should work too
assert(!__traits(compiles,
takesNotNull(assumeNotNull(null)))); // notNull(null) shouldn't
compile
test = null; // reset our pointer
assertThrown!AssertError(takesNotNull(assumeNotNull(test)));
// test is null now, so this should throw an assert failure
void takesConstNotNull(in NotNull!(int *) a) {}
test = &dummy; // make it valid again
takesConstNotNull(assumeNotNull(test)); // should Just Work
NotNull!(int*) foo2 = foo; // we should be able to assign
NotNull to other NotNulls too
foo2 = foo; // including init and assignment
}
=========
One thing I wasn't quite happy with was a lot of people request:
if(a !is null) {
// a is now NotNull!T
}
Of course, that directly won't work, but maybe we can get close.
Best I could do was
if(a !is null) {
auto a2 = assumeNotNull(a);
// use a2 instead
}
which isn't quite what people wanted, but it is simple and works.