3

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

/** 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 figured out how to allow

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

to compile which I believe should work automatically.

The alias this is obviously not sufficient for this work.

How do I make that happen?

See also: http://forum.dlang.org/thread/aprsozwvnpnchbaswjxd@forum.dlang.org#post-aprsozwvnpnchbaswjxd:40forum.dlang.org

Complete source of module notnull.d follows:

#!/usr/bin/env rdmd-dev-module

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);
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Nordlöw
  • 11,838
  • 10
  • 52
  • 99
  • 1
    I don't think it is possible to get it fully correct in D today. We could try an alias this up each base class, then add opDispatch or opDot to access functions in the derived class (since alias this wouldn't work for that since it now points to the base), but even this doesn't cover all interfaces. Maybe when multiple alias this is implemented (don't hold your breath, been waiting for that a long time), but otherwise.... blargh. Maybe we should ask for a new feature since this breaks a lot of wrapped types. – Adam D. Ruppe Feb 26 '14 at 04:11
  • I agree. Making wrapped types such these just work is an important issue. This is my highest wish for 2.066. Should I send a request somewhere for this? – Nordlöw Feb 26 '14 at 10:20

1 Answers1

0

As for me, this is bad idea, because NotNull is just a container for exactly one value, and covariant/contravariant containers are not safe in sense of runtime integrity. Let's imagine this idea is implemented and consider this slightly modified example:

unittest
{
    class A {}
    class B : A { void bb(){} }

    void f(ref NotNull!A a) {
        a = assumeNotNull(new A);
    }

    NotNull!B b = assumeNotNull(new B);
    f(b);
    b.bb();
}

b.bb() call on A object, and if f() resides in another module then compiler don't have a chance to catch this issue.

The only right way to make it covariant is to remove all consuming methods (opAssign, fromMsgpack) making it in fact immutable.

Additional reading: Variance in C++, Cheat Codes

crimaniak
  • 123
  • 8