Difficulties with non-nullable types (part 1)

For those of you who don't read the comments made on other posts of
mine, you might be unaware about a little conversation that's been
going on about nullable types in this post
Both Stuart and DrPizza have made extremely good points about the
drawbacks of our current implementation and we are taking their
feedback very seriously and thinking about if there are ways in which
we can improve the situation.  Now, the subject matter also
drifted from nullable value types in specific to our entire type system
in general.  

Given the chance to do the C# language all over again i can say that
without question i would want to unify the issues of null/non-null and
reference/value types.  In C# 1.0 we supported non-nullable value
types, and nullable reference types, and in C# 2.0 we’ve added nullable
value types, but we’re still lacking non-nullable reference types in
the mix.  (also, some would claim that our implementation of
nullable value types leaves a lot to be desired).   
 

First: What is a non-nullable type?  Well, currently our type
system reserves a sentinel value “null” for every reference type in the
system.  So, if you declare a member or local to be of some type
(say string), then you can either put an instance of that type in that
slot, or you can use the sentinel value “null”.  With non-nullable
types you can say that you don’t want to allow this sentinel value to
be used and only instances are allowed.   For example, very
often one sees code like this:

 class Person {
    string name;

    public Person(string name) {
        if (name == null) {
            throw new ArgumentNullException("name");
        }

        this.name = name;
    }
}

In this example “name” can never be null and the constructor seeks to
enforce that.  This means that later code in the class never needs
to worry if that field might be null.  This is helpful so that you
can write simple code like:

     public override int GetHashCode() {
         return name.GetHashCode();
    }

Now, this approach is somewhat error prone.  Say you add a constructor later of the form:

     public Person(string name, int age) {
        this.name = name;
        this.age = age;
    }

Whoops!  We forgot to make sure that “name” isn’t
null.   And, later on down the line you’re going to fail at
runtime when someone calls the GetHashCode method and gets a
NullReferenceException.  

So what can we do to help prevent this?  Well, by adding a new
language construct we could tell the compiler and runtime “this field
is not allowed to be null”, and thus have compile time protection from
this sort of thing happening.  Here’s one way that this could look:

  class Person {
    string! name;

    public Person(string! name) {
        this.name = name;
    }

    public override int GetHashCode() {
         return name.GetHashCode();
    }
}

Looks the same as the original with only a slight change.  The
type of “name” is now “string!” instead of “string”.  The
exclamation point tells the type system “null is not an allowable value
for this field”.  Because of this we no longer need to check for
null in the constructor, and if we add other constructors in the future
the compiler will make sure we don’t violate this.  For example,
if we tried to add

     public Person(string name) {
        this.name = name;
    }

Then we would get an error saying “you cannot assign a value of type
string into a field of type string! until you have verified that its
value is not null”.  Also, as a side benefit, you would get a
small performance enhancement out of this as the call to GetHashCode no
longer needs to check of “name” is null or not.  Since “name”
could never be null, the virtual call can just be made directly. 
This is similar to the benefits that you get with generics.  Added
typesafety, and performance to boot!

I mentioned wanting this feature quite a while back in this blog
post
.  However, I never went over the details of why non-nullable
types are hard.  In my upcoming posts I’m going to discuss the
problems that need to be overcome if we wanted to make this feature a
reality in the future.