add a simple NotNull struct #477

Closed
wants to merge 10 commits into
from

Projects

None yet

7 participants

@adamdruppe

I've talked about implementing these a lot of times on the newsgroup, but I don't see one in Phobos yet!

This is my latest idea on how to implement a NotNull type with tests and documentation, added
to std.typecons (right below Nullable).

I haven't actually used any NotNull in practice, but from the attached tests, it looks like it will work well - hence, the pull request.

I'm open to any comments, however.

@klickverbot klickverbot and 1 other commented on an outdated diff Mar 5, 2012
std/typecons.d
+ 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(notNull(test)); // this should work too
+ assert(!__traits(compiles, takesNotNull(notNull(null)))); // notNull(null) shouldn't compile
+ test = null; // reset our pointer
+
+ try
+ {
+ takesNotNull(notNull(test)); // test is null now, so this should throw an assert failure
+ assert(0, "it let us pass a null value to a NotNull function");
@klickverbot
klickverbot Mar 5, 2012 D Programming Language member

Isn't this going to be caught by the catch clause as well? See assertThrown and friends.

@adamdruppe
adamdruppe Mar 5, 2012

Oh, you're right! Duh. Changed to assertThrown!AssertError

@ghost
ghost commented Mar 5, 2012

Shouldn't assert(rhs !is null); be changed to enforce(rhs !is null);?

@adamdruppe

I was thinking of the not null as being similar to a contract; it looks for usage errors in the program rather than something like user input, so assert is the thing. I think assert is right.

@alexrp
Member
alexrp commented Mar 18, 2012

FWIW, I agree that we should use assert for this.

@alexrp alexrp commented on an outdated diff Mar 18, 2012
std/typecons.d
+ }
+
+ @disable this(typeof(null)); /// the null literal can be caught at compile time
+
+ @disable typeof(this) opAssign(typeof(null)); /// ditto
+
+ /// does a runtime null check on assignment, to ensure we never store null
+ typeof(this) opAssign(T rhs)
+ {
+ assert(rhs !is null);
+ t = rhs;
+ return this;
+ }
+}
+
+/// A convenience function to construct a NotNull value. If you pass it null, it will throw.
@alexrp
alexrp Mar 18, 2012 D Programming Language member

Should be "it will assert"?

@klickverbot
Member

Maybe an assumeNonNull/checkNonNull pair would provide a nice unified entry point to the NonNull universe?

@JakobOvrum
Member

How about adding an opAssign overload for T (which would assert)?

Also, this type does not work for all possible types T, thus it should have a template constraint.

@adamdruppe

I actually had the opAssign before, but removed it in tonight's commit. The reason is you might want the type system to catch an accidental assignment so you can make a decision - check null or assume null - at that time and write it explicitly.

The same logic could apply to the constructor, but the fact that you can't use auto is annoying enough as it is, and having to do NotNull!T t = assumeNotNull(new T()); is probably just too far.

@JakobOvrum
Member

I agree, the transition from nullable to non-nullable should be explicit when possible, it just seemed a little out of sync with the constructor. I suppose construction isn't a problem because the name of the type, NotNull, will always be explicit at least once.

@JesseKPhillips JesseKPhillips commented on the diff Apr 22, 2012
std/typecons.d
+ {
+ 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)
@JesseKPhillips
JesseKPhillips Apr 22, 2012

I think this function should contain an assert(t !is null)

@klickverbot
klickverbot Apr 22, 2012 D Programming Language member

The constructor asserts.

@klickverbot klickverbot commented on an outdated diff Apr 22, 2012
std/typecons.d
+ @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);
+}
+
+/// A convenience function to check for null. If you pass null, it will throw an exception. Otherwise, return NotNull!T.
+NotNull!T checkNotNull(T)(T t)
@klickverbot
klickverbot Apr 22, 2012 D Programming Language member

I know I had that name in my previous comment, but maybe we should call this enforceNotNull for consistency – opinions?

@jmdavis jmdavis commented on the diff May 4, 2012
std/typecons.d
+ * 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.
+
+ 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.
+ ---
+*/
@jmdavis
jmdavis May 4, 2012 D Programming Language member

There are tabs in the comments. Please replace them with spaces.

@jmdavis jmdavis commented on the diff May 4, 2012
std/typecons.d
+ 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(__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); }
@jmdavis
jmdavis May 4, 2012 D Programming Language member

Have you reported the bug?

@andralex
Member
andralex commented Jul 2, 2012

This seems to be languishing. @adamdruppe, still there?

@adamdruppe

I've been very busy the last couple months with a lot of
real life stuff.

Going to be settling down again soon; moving this week
and then thing should start returning to normal.

On Sun, Jul 01, 2012 at 07:02:27PM -0700, Andrei Alexandrescu wrote:

This seems to be languishing. @adamdruppe, still there?


Reply to this email directly or view it on GitHub:
#477 (comment)

@andralex
Member
andralex commented Jul 9, 2012

waiting

@andralex
Member

Will close this for now, please reopen as fit. Thanks!

@andralex andralex closed this Jul 16, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment