Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

add a simple NotNull struct #477

Closed
wants to merge 10 commits into from

8 participants

Adam D. Ruppe Andrej Mitrovic Alex Rønne Petersen David Nadlinger JakobOvrum Andrei Alexandrescu Jesse Phillips Jonathan M Davis
Adam D. Ruppe

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.

std/typecons.d
((90 lines not shown))
  1675
+    bar(test);
  1676
+    bar(foo);
  1677
+
  1678
+    void takesNotNull(NotNull!(int*) a) { }
  1679
+
  1680
+    assert(!__traits(compiles, takesNotNull(test))); // should not work; plain int might be null
  1681
+    takesNotNull(foo); // should be fine
  1682
+
  1683
+    takesNotNull(notNull(test)); // this should work too
  1684
+    assert(!__traits(compiles, takesNotNull(notNull(null)))); // notNull(null) shouldn't compile
  1685
+    test = null; // reset our pointer
  1686
+
  1687
+    try
  1688
+    {
  1689
+        takesNotNull(notNull(test)); // test is null now, so this should throw an assert failure
  1690
+	assert(0, "it let us pass a null value to a NotNull function");
2
David Nadlinger Collaborator

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

Adam D. Ruppe
adamdruppe added a note March 05, 2012

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
Andrej Mitrovic
Collaborator

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

Adam D. Ruppe

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.

Alex Rønne Petersen
Collaborator

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

std/typecons.d
((36 lines not shown))
  1621
+    }
  1622
+
  1623
+    @disable this(typeof(null)); /// the null literal can be caught at compile time
  1624
+
  1625
+    @disable typeof(this) opAssign(typeof(null)); /// ditto
  1626
+
  1627
+    /// does a runtime null check on assignment, to ensure we never store null
  1628
+    typeof(this) opAssign(T rhs)
  1629
+    {
  1630
+        assert(rhs !is null);
  1631
+        t = rhs;
  1632
+        return this;
  1633
+    }
  1634
+}
  1635
+
  1636
+/// A convenience function to construct a NotNull value. If you pass it null, it will throw.
1
Alex Rønne Petersen Collaborator
alexrp added a note March 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
David Nadlinger
Collaborator

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

JakobOvrum

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.

Adam D. Ruppe

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

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.

Jesse Phillips JesseKPhillips commented on the diff April 22, 2012
std/typecons.d
((43 lines not shown))
  1628
+    {
  1629
+        assert(value !is null);
  1630
+        _notNullData = value;
  1631
+    }
  1632
+
  1633
+    @disable this(typeof(null)); /// the null literal can be caught at compile time
  1634
+    @disable typeof(this) opAssign(typeof(null)); /// ditto
  1635
+    NotNull!T opAssign(NotNull!T rhs)
  1636
+    {
  1637
+        this._notNullData = rhs._notNullData;
  1638
+        return this;
  1639
+    }
  1640
+}
  1641
+
  1642
+/// A convenience function to construct a NotNull value from something you know isn't null.
  1643
+NotNull!T assumeNotNull(T)(T t)
2

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

David Nadlinger Collaborator

The constructor asserts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
std/typecons.d
((49 lines not shown))
  1634
+    @disable typeof(this) opAssign(typeof(null)); /// ditto
  1635
+    NotNull!T opAssign(NotNull!T rhs)
  1636
+    {
  1637
+        this._notNullData = rhs._notNullData;
  1638
+        return this;
  1639
+    }
  1640
+}
  1641
+
  1642
+/// A convenience function to construct a NotNull value from something you know isn't null.
  1643
+NotNull!T assumeNotNull(T)(T t)
  1644
+{
  1645
+	return NotNull!T(t);
  1646
+}
  1647
+
  1648
+/// A convenience function to check for null. If you pass null, it will throw an exception. Otherwise, return NotNull!T.
  1649
+NotNull!T checkNotNull(T)(T t)
1
David Nadlinger Collaborator

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
Jonathan M Davis jmdavis commented on the diff May 03, 2012
std/typecons.d
((10 lines not shown))
  1595
+	* If you assign a null value at runtime to it, it will immediately throw an Error
  1596
+	  at the point of assignment.
  1597
+
  1598
+        NotNull!T can be substituted for T at any time, but T cannot become
  1599
+	NotNull without some attention: either declaring NotNull!T, or using
  1600
+	the convenience function, notNull.
  1601
+
  1602
+	Examples:
  1603
+	---
  1604
+		int myInt;
  1605
+		NotNull!(int *) not_null = &myInt;
  1606
+		// you can now use variable not_null anywhere you would
  1607
+		// have used a regular int*, but with the assurance that
  1608
+		// it never stored null.
  1609
+	---
  1610
+*/
1
Jonathan M Davis Collaborator
jmdavis added a note May 03, 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
Jonathan M Davis jmdavis commented on the diff May 03, 2012
std/typecons.d
((20 lines not shown))
  1605
+		NotNull!(int *) not_null = &myInt;
  1606
+		// you can now use variable not_null anywhere you would
  1607
+		// have used a regular int*, but with the assurance that
  1608
+		// it never stored null.
  1609
+	---
  1610
+*/
  1611
+struct NotNull(T) if(__traits(compiles, { T t; assert(t is null); }))
  1612
+{
  1613
+    private T _notNullData;
  1614
+    @property inout(T) _notNullDataHelper() inout
  1615
+    {
  1616
+        assert(_notNullData !is null); // sanity check of invariant
  1617
+        return _notNullData;
  1618
+    }
  1619
+    // Apparently a compiler bug - the invariant being uncommented breaks all kinds of stuff.
  1620
+    // invariant() { assert(_notNullData !is null); }
1
Jonathan M Davis Collaborator
jmdavis added a note May 03, 2012

Have you reported the bug?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Andrei Alexandrescu
Owner

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

Adam D. Ruppe
Andrei Alexandrescu
Owner

waiting

Andrei Alexandrescu
Owner

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

Andrei Alexandrescu andralex closed this July 15, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.

Showing 1 changed file with 129 additions and 0 deletions. Show diff stats Hide diff stats

  1. 129  std/typecons.d
129  std/typecons.d
@@ -1586,6 +1586,135 @@ unittest
1586 1586
 }
1587 1587
 
1588 1588
 /**
  1589
+	NotNull ensures a null value can never be stored.
  1590
+
  1591
+	* You must initialize it when declared
  1592
+
  1593
+	* You must never assign the null literal to it (this is a compile time error)
  1594
+
  1595
+	* If you assign a null value at runtime to it, it will immediately throw an Error
  1596
+	  at the point of assignment.
  1597
+
  1598
+        NotNull!T can be substituted for T at any time, but T cannot become
  1599
+	NotNull without some attention: either declaring NotNull!T, or using
  1600
+	the convenience function, notNull.
  1601
+
  1602
+	Examples:
  1603
+	---
  1604
+		int myInt;
  1605
+		NotNull!(int *) not_null = &myInt;
  1606
+		// you can now use variable not_null anywhere you would
  1607
+		// have used a regular int*, but with the assurance that
  1608
+		// it never stored null.
  1609
+	---
  1610
+*/
  1611
+struct NotNull(T) if(__traits(compiles, { T t; assert(t is null); }))
  1612
+{
  1613
+    private T _notNullData;
  1614
+    @property inout(T) _notNullDataHelper() inout
  1615
+    {
  1616
+        assert(_notNullData !is null); // sanity check of invariant
  1617
+        return _notNullData;
  1618
+    }
  1619
+    // Apparently a compiler bug - the invariant being uncommented breaks all kinds of stuff.
  1620
+    // invariant() { assert(_notNullData !is null); }
  1621
+
  1622
+    alias _notNullDataHelper this; /// this is substitutable for the regular (nullable) type
  1623
+    @disable this();
  1624
+
  1625
+    // this could arguably break the static type check because
  1626
+    // you can assign it from a variable that is null.. but I
  1627
+    // think it is important that NotNull!Object = new Object();
  1628
+    // works, without having to say assumeNotNull(new Object())
  1629
+    // for convenience of using with local variables.
  1630
+
  1631
+    /// constructs with a runtime not null check (via assert())
  1632
+    this(T value)
  1633
+    {
  1634
+        assert(value !is null);
  1635
+        _notNullData = value;
  1636
+    }
  1637
+
  1638
+    @disable this(typeof(null)); /// the null literal can be caught at compile time
  1639
+    @disable typeof(this) opAssign(typeof(null)); /// ditto
  1640
+
  1641
+    /// .
  1642
+    NotNull!T opAssign(NotNull!T rhs)
  1643
+    {
  1644
+        this._notNullData = rhs._notNullData;
  1645
+        return this;
  1646
+    }
  1647
+}
  1648
+
  1649
+/// A convenience function to construct a NotNull value from something you know isn't null.
  1650
+NotNull!T assumeNotNull(T)(T t)
  1651
+{
  1652
+    return NotNull!T(t); // note the constructor asserts it is not null
  1653
+}
  1654
+
  1655
+/// A convenience function to check for null. If you pass null, it will throw an exception. Otherwise, return NotNull!T.
  1656
+NotNull!T enforceNotNull(T)(T t)
  1657
+{
  1658
+    enforce(t !is null);
  1659
+    return NotNull!T(t);
  1660
+}
  1661
+
  1662
+unittest
  1663
+{
  1664
+    import core.exception;
  1665
+    import std.exception;
  1666
+
  1667
+    void NotNullCompiliationTest1()() // I'm making these templates to defer compiling them
  1668
+    {
  1669
+        NotNull!(int*) defaultInitiliation; // should fail because this would be null otherwise
  1670
+    }
  1671
+    assert(!__traits(compiles, NotNullCompiliationTest1!()()));
  1672
+
  1673
+    void NotNullCompiliationTest2()()
  1674
+    {
  1675
+        NotNull!(int*) defaultInitiliation = null; // should fail here too at compile time
  1676
+    }
  1677
+    assert(!__traits(compiles, NotNullCompiliationTest2!()()));
  1678
+
  1679
+    int dummy;
  1680
+    NotNull!(int*) foo = &dummy;
  1681
+
  1682
+    assert(!__traits(compiles, foo = null)); // again, literal null is caught at compile time
  1683
+
  1684
+    int* test;
  1685
+
  1686
+    test = &dummy;
  1687
+
  1688
+    foo = assumeNotNull(test); // should be fine
  1689
+
  1690
+    void bar(int* a) {}
  1691
+
  1692
+    // these should both compile, since NotNull!T is a subtype of T
  1693
+    bar(test);
  1694
+    bar(foo);
  1695
+
  1696
+    void takesNotNull(NotNull!(int*) a) { }
  1697
+
  1698
+    assert(!__traits(compiles, takesNotNull(test))); // should not work; plain int might be null
  1699
+    takesNotNull(foo); // should be fine
  1700
+
  1701
+    takesNotNull(assumeNotNull(test)); // this should work too
  1702
+    assert(!__traits(compiles, takesNotNull(assumeNotNull(null)))); // notNull(null) shouldn't compile
  1703
+    test = null; // reset our pointer
  1704
+
  1705
+    assertThrown!AssertError(takesNotNull(assumeNotNull(test))); // test is null now, so this should throw an assert failure
  1706
+
  1707
+    void takesConstNotNull(in NotNull!(int *) a) {}
  1708
+
  1709
+    test = &dummy; // make it valid again
  1710
+    takesConstNotNull(assumeNotNull(test)); // should Just Work
  1711
+
  1712
+    NotNull!(int*) foo2 = foo; // we should be able to assign NotNull to other NotNulls too
  1713
+    foo2 = foo; // including init and assignment
  1714
+}
  1715
+
  1716
+
  1717
+/**
1589 1718
 $(D BlackHole!Base) is a subclass of $(D Base) which automatically implements
1590 1719
 all abstract member functions in $(D Base) as do-nothing functions.  Each
1591 1720
 auto-implemented function just returns the default value of the return type
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.