Why is Null obsolete

I believe that introduction of nulls into high level languages (Java, C# etc.) was a mistake. But sometimes I meet developers who believe that nulls are necessary and thus justified. I disagree with that, but it’s not always easy to explain why.

Why do we need nulls?

The main reasoning null apologists usually have is the need to represent some special cases like:

  • element doesn’t exist
  • not initialized (yet)
  • operation wasn’t successful
  • some special return value which can’t be expressed in the return type — e.g. method ValidationErrors validateForm(Form form) will return null, if validation is successful.

I agree that we have a need to cover these cases, but I believe that there are much better ways than using null.

Union types

See following examples how we could specify methods in Ceylon with its union types:

Element|None findElementById(int id) { ... }
Result|ProcessingError processData() { ... }
Success|ValidationErrors validateForm(Form form) { ... }

In the first example, method signature specifies that method can return either instance of type Element or instance of type None. It’s quite similar to C’s union, but with the added safety.

Aren’t these much more clear? You don’t have to second guess what null is supposed to mean in this case.

Let’s continue with following:

value el = findElementById(id);
el.doSomething(); // syntax error here

Oops, compiler complains that you can’t call doSomething() on the el value.
This is because findElementById() doesn’t return instance of type Element (which contains method doSomething()), it returns instance of type Element|None. There’s no doSomething() method on Element|None type so you get compilation error.

To do something you are forced to explicitly check the type:

value el = findElementById(id);
if (is Element el) {
// here ceylon knows that el is Element so you can safely
// call .doSomething()

You can argue that this is just standard good practice in the software development. The difference here is that compiler does actually enforce the good practice so you never get NullPointerException.

Are there really no nulls in Ceylon?

Ceylon didn’t actually get rid of nulls altogether — there’s a Null class which you can use with the union types, so you can declare method:

Element|Null findElementById(int id);

The difference here is that Null isn’t some special magic value in the type system. It’s class like any other, you can’t assign Null instance to any other type, you can’t call any method on it (it doesn’t have any). It’s so different that the “Null” name is probably bit misleading.

How sweet do you like your tea?

Ceylon also has some syntactic sugar specifically for the Null class, so you can use Element? instead of Element|Null and safe access .? notation like in Kotlin:

Element? findElementById(int id);

You lose a bit on the expressiveness (again what’s Null in this case supposed to mean?) but type safety is still there and the code is short.
It’s not totally clear cut when it’s better to use Null class and when you should define special class — in the case of this findElementById() I’d probably be fine with Null because it’s quite natural and expected. In less obvious cases (like an example above when “validateForm” returns null for success) I think it’s much clearer to define special type for validation success.

Null is a gaping hole in the type system

Consider null for a second — it’s really weird value from the type system point of view. What is the type of null? It can be assigned to variable of any reference type, yet you can’t treat it like instance of any type (you’ll get an exception).

We have this fancy static type systems guaranteeing that we can’t mistakenly treat (for example) List as a string which would fail in the runtime, yet we have this gaping hole which makes potentially any method call to throw exception. And it’s not just theoretical problem, NullPointerException causes many failures in the real world programming.

It’s like building this nice super secure car, but omitting to put in seat belts. Yeah, the car has super strong frame, nice airbags, but if you make a simple mistake, you end up dead anyway.

My main point in this post is that there isn’t any use case for nulls (as implemented in Java, C# etc.) which can’t be covered in much better way by stronger type system. Ergo, nulls should die.