Getting NotNull Right

"Nordlöw" per.nordlow at gmail.com
Wed Feb 5 13:14:51 PST 2014


Hi!

I've changed Adam D Ruppes module notnull.d a bit to allow 
assignment of a NotNull inherited class instance to a NotNull 
base class using

     /** Assignment from $(D NotNull) Inherited Class $(D rhs) to 
$(D NotNull) Base
         Class $(D this). */
     typeof(this) opAssign(U)(NotNull!U rhs) @safe pure nothrow if 
(isAssignable!(T, U)) {
         this._value = rhs._value;
         return this;
     }

This allows

unittest
{
     class A {}
     class B : A {}
     NotNull!B b = assumeNotNull(new B);
     NotNull!A a = assumeNotNull(new A);
     a = b;
     assert(a is b);
}

However I haven't figure out how to allow the follow code to 
compile

unittest
{
     class A {}
     class B : A {}
     void f(NotNull!A a) {}
     NotNull!B b = assumeNotNull(new B);
     f(b);
}

which I believe should work automatically.

How do I make that happen?

Complete source of module notnull.d follows:



#!/usr/bin/env rdmd

module notnull;

import std.traits: isAssignable;

/** Note that NotNull!T is not NotNullable :) */
alias NotNullable(T) = isAssignable!(T, typeof(null));

/**
    NotNull ensures a null value can never be stored.

    * You must initialize it when declared

    * You must never assign the null literal to it (this is a 
compile time error)

    * If you assign a null value at runtime to it, it will 
immediately throw an Error
    at the point of assignment.

    NotNull!T can be substituted for T at any time, but T cannot 
become
    NotNull without some attention: either declaring NotNull!T, or 
using
    the convenience function, notNull.

    Condition: T must be a reference type.
    Instead of: __traits(compiles, { T t; assert(t is null); }.

    TODO: Merge with http://arsdnet.net/dcode/notnullsimplified.d

    Examples:
    ---
    int myInt;
    NotNull!(int *) not_null = &myInt;
    // you can now use variable not_null anywhere you would
    // have used a regular int*, but with the assurance that
    // it never stored null.
    ---
*/
struct NotNull(T) if (NotNullable!T)
{
     @disable this(); // Disallow default initialized (to null)

     /** Assignment from $(D NotNull) Inherited Class $(D rhs) to 
$(D NotNull) Base
         Class $(D this). */
     typeof(this) opAssign(U)(NotNull!U rhs) @safe pure nothrow if 
(isAssignable!(T, U)) {
         this._value = rhs._value;
         return this;
     }

     NotNull!U opCast(U)() @safe pure nothrow if (isAssignable!(U, 
T)) {
         return NotNull!_value;
     }

     // 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) @safe pure nothrow
     {
         assert(value !is null);
         _value = value;
     }

     /** Disable null construction. */
     @disable this(typeof(null));
     /** Disable null assignment. */
     @disable typeof(this) opAssign(typeof(null));

     private T _value;
     @property inout(T) _valueHelper() inout
     {
         assert(_value !is null); // sanity check of invariant
         return _value;
     }
     // Apparently a compiler bug - the invariant being 
uncommented breaks all kinds of stuff.
     // invariant() { assert(_value !is null); }

     alias _valueHelper this; /// this is substitutable for the 
regular (nullable) type

     /* void toMsgpack  (Packer)  (ref Packer packer) const { 
packer.pack(_value); } */
     /* void fromMsgpack(Unpacker)(auto ref Unpacker unpacker) { 
unpacker.unpack(_value); } */
}

/** A convenience function to construct a NotNull value from 
something $(D t)
     you know isn't null.
*/
NotNull!T assumeNotNull(T)(T t) if (NotNullable!T)
{
     return NotNull!T(t); // note the constructor asserts it is 
not null
}

/** A convenience function to check for null $(D t). If you pass 
null to $(D t),
     it will throw an exception. Otherwise, return NotNull!T.
*/
NotNull!T enforceNotNull(T, string file = __FILE__, size_t line = 
__LINE__)(T t) if (NotNullable!T)
{
     import std.exception: enforce;
     enforce(t !is null, "t is null!", file, line);
     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

}

unittest
{
     class A {}
     class B : A {}
     NotNull!B b = assumeNotNull(new B);
     NotNull!A a = assumeNotNull(new A);
     a = b;
     assert(a is b);
}


More information about the Digitalmars-d-learn mailing list