add a simple NotNull struct #477

Closed
wants to merge 10 commits into from

5 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
D Programming Language member

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ghost

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
D Programming Language member

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
D Programming Language member
alexrp added a note Mar 18, 2012

Should be "it will assert"?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@klickverbot
D Programming Language member

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

@JakobOvrum
D Programming Language 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
D Programming Language 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)

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

@klickverbot
D Programming Language member

The constructor asserts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@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
D Programming Language member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@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
D Programming Language member
jmdavis added a note May 4, 2012

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@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
D Programming Language member
jmdavis added a note May 4, 2012

Have you reported the bug?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@andralex
D Programming Language member

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

@adamdruppe
@andralex
D Programming Language member

waiting

@andralex
D Programming Language 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