Ignoring a Billion-Dollar Mistake is simply not an Option

But are nullable reference types the answer?

Published on 06 June 2019

A 'Billion-Dollar Mistake'?

In 1965, computer scientist and otherwise all round good egg, Sir Charles Antony Richard Hoare (Tony to most) unleashed the 'null reference' upon an unsuspecting world, simply because it was "so easy". Five decades later, the vast majority of computer programmers remain all too au fait with the "null reference" exception, battling it's seemingly unstoppable encroachment into otherwise flawless program flows.

In 2009, Tony apologised for inventing the null reference, stating:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

"Innumerable errors, vulnerabilities, and system crashes"? How so? Well, in most programming languages, null is not just a valid value for a reference type, it is the default value. This means that any uninitialized reference type variable will cause a null reference exception if it is dereferenced. Furthermore, because null is the default reference type value, it tends to be the value returned when a process cannot be completed/calculated correctly causing otherwise innocuous dependent code to become inherently error prone. For example:

string value = ParseValue(); // <= returns null as value cannot be parsed
Console.WriteLine($"Value contains {value.Length} characters"); // <= Boom!

Yes, we all look at the above code and shrug it off with a "D'uh, of course the value should be null checked". But why? Why, in a statically typed and run-time checked language should there exist such a simple - and indeed default - means of causing an exception when, by definition, exceptions should be exceptional.

C# 8.0 to the rescue?

With the rise in prominence of functional languages (particularly F#) which do not suffer this issue (at least by default), the C# community is working to address this mistake with the release of a major new feature in version 8.0 of the language: Nullable reference types.

Firstly, kudos to the community for calling the feature "Nullable reference types" rather than "Non-nullable reference types" inferring that nullability is the "non-default behaviour" you can have if you explicitly want. Unfortunately, the implementation of this feature is, in my opinion, completely hamstrung by backwards compatibility. Specifically:

  1. It will (in it's current guise) only ever cause compiler warnings not errors. Obviously you can enable warnings as errors but this isn't always feasible on large legacy projects.
  2. It must be specifically enabled at a project level - potentially a large undertaking on legacy projects - or via messy directives in code.
  3. It can't be applied consistently in all cases or at all in some cases. For example Jon Skeet's IEqualityComparer example here or the "Generics and nullable types" section of this InfoQ post.
  4. Mixing code using "null reference types" with class libraries which have not implemented this functionality is likely to be extremely messy in the short to medium term requiring "nullable shims" etc.
  5. In the majority of cases, it doesn't actually remove the need for null reference checks.

To me the "Nullable reference types" feature feels a bit like the proverbial hammer used to crack a nut. Except that, in this instance, the hammer isn't actually capable of cracking the nut, it is just a vehicle for the label on the side providing instructions on how to get the nut cracked.

Ok, that's a little facetious. On new or small code-bases, I'm sure utilizing "nullable reference types" will pay dividends and I would support it's use whole-heartedly. But what about large legacy code bases where having to specifically enable "nullable reference types" or enabling "warnings as errors" might not be viable.

Perhaps there's another Option?

The more astute readers out there have no doubt have seen where this blog post is heading due to my nod to F# above and the not so subtle use of the word Option. As a functional language, F# eschews the use of null (unless specifically enabled on a type by type basis) by explicitly modelling the concept of "nothing-able" with a type: the discriminated union type called Option.

Here's a description of the Option type from the FSharp Language Reference:

The option type is a simple discriminated union in the F# core library. The option type is declared as follows.

// The option type is a discriminated union.
type Option<'a> =
    | Some of 'a
    | None

The previous code specifies that the type Option is a discriminated union that has two cases, Some and None. The Some case has an associated value that consists of one anonymous field whose type is represented by the type parameter 'a. The None case has no associated value. Thus the option type specifies a generic type that either has a value of some type or no value.

In short, instead of allowing a value to be null, the value is wrapped inside another object which is able to explicitly say whether the value is something or nothing. While this is an F# concept, it can be mirrored quite simply in C# as follows:

public struct Option<T>
{
    public static readonly Option<T> None = new Option<T>();

    public static Option<T> Some(T value)
    {
        return new Option<T>(value);
    }

    private Option(T value)
    {
        IsSome = true;
        Value = value;
    }

    public T Value { get; private set; }
    public bool IsSome { get; private set; }
    public bool IsNone => !IsSome;
}

Note that this is a struct (i.e. a value type which cannot be null) but also generic meaning it can hold a reference type value. Using this class we can write code which in which we can be confident that we won't encounter a null reference:

var option = GetSomeOption();
if (option.IsSome)
{
    Console.WriteLine($"Found a value of {option.Value}");
}
else
{
    Console.WriteLine("Did not find a value");
}

What's that? This code looks exactly like the code for a null reference check? True, except for three important differences:

  1. The use of an Option as the return type indicates to the consumer that "None" is a valid value and must be considered - as opposed to a null which may or may not be deliberate.
  2. The Value property must be explicitly read from the Option meaning the consumer is less likely to access the property without checking that it 'IsSome' first.
  3. You can do more with something than you can with nothing; read on.

Regarding that last point, once we have something - i.e. an Option type - we can work with it to facilitate it's use in code. For example, consider the following extension methods:

/// <summary>
/// Projects an <see cref="Option{T}"/> of <typeparamref name="TSource"/> to
/// an <see cref="Option{T}"/> of <typeparamref name="TDest"/> using the
/// specified <paramref name="projection"/>
/// </summary>
public static Option<TDest> Select<TSource, TDest>(this Option<TSource> source, Func<TSource, Option<TDest>> projection)
{
    return source.IsSome ? projection(source.Value) : None<TDest>();
}

/// <summary>
/// If the <see cref="Option{T}.IsSome"/> returns true for the <see cref="Option{T}"/> specified in <paramref name="source"/>
/// then this method will return it's <see cref="Option{T}.Value"/>, otherwise the value of the <paramref name="value"/> parameter
/// is returned
/// </summary>
public static T Coalesce<T>(this Option<T> source, T value)
{
    return source.IsSome ? source.Value : value;
}

Using these we can move away from imperative property checking an adopt a more declarative style:

var response = GetSomeOption()
    .Select(value => $"Found a value of {value}")
    .Coalesce("Did not find a value");

Console.WriteLine(response);

The benefits of declarative code vs imperative code will likely be the subject of my next "contentious" blog post. I have repeatedly debated the various pros and cons of declarative code with co-workers and clients and still believe it's a fundamentally better way of expressing intent while simultaneously and writing "safer" code.

So anyway, notice anything about the Option type?

Oh, isn't this one of those... err... gonad things?

Monad? Yes. The Option type is, in my opinion, the quintessential monad as, when combined with compositional functions like those shown above, it is able to provide "a design pattern that allows structuring programs generically while automating away boilerplate code needed by the program logic"; i.e. removing all those damn ugly null checks!

After working with the Option type for a number of years across numerous project's I've accumulated (mostly by plagiarizing F#) a number of monadic functions which can almost eliminate imperative code (at least with respect to null checking). These functions are especially useful for collections as shown (in the somewhat contrived example) below:

var result = Enumerable
    .Range(1, 100)
    .Select(value => ReturnSomeIfAValidValueOtherwiseNone(value)) // <= Returns an Option
    .Collect() // <= Do not propagate values of Option.None
    .FirstOption() // <= Take the first value of Option.Some or return Option.None if none exist
    .Select(value => $"{value} is the first valid value")
    .Coalesce("No valid values found");

Ok, so where does this get us?

Well, looking back and comparing this with "nullable reference types", I believe explicitly modelling the concept of nothing using the Option type provides many advantages such as:

  1. You can adopt this approach incrementally - one method at a time if need be - without huge numbers of warnings or messy code directives.
  2. Explicitly stating that a method can return "nothing" with the Option type is a much more obvious means of expressing intent than a "nullable reference type".
  3. You can finally get rid of null reference checks!
  4. Returning something rather than nothing facilitates the adoption of a more declarative programming style.

So there we go. A safe, obvious, easy and, most-importantly, low-cost means of dealing with the "Billion-Dollar Mistake". Give it a stab and see what you think. As shown above, there's a very low barrier to entry so there's very little to lose.