In a language like Java you often have to set a value to null and then later instantiate it to a valid value. Much of the standard library also uses null to indicate the absence of data. For example, if you attempt to ‘get’ an item from a map in Java, the item returned will be ‘null’ if it does not already exist.

Much of the time null feels like an annoyance, and Java 8 has made tremendous improvements to the ergonomics of this situatoin by introducting the Optional class. But much of the ecosystem, legacy code, and standard library still relies on ‘null’ values.

And that’s a pain.

I won’t pretend that NPE is the hardest bug to fix or track down. I have generally found that NPE’s are not too tricky.. Of course, sometimes the null has propagated through the system and then it’ can be a real pain’.Regardless, NPE is annoying

  • even if the bug is fixed quickly I’m going to spend time redeploying the service to get the patch out there. There is a constant amount of time outside of tracking down the bug that will always be there.

There is no question in my mind that I would be more productive if I did not have to:

a) Remediate NPE b) Write numerous testcases in which I explicitly specify null values to ensure that they are appropriately handled

Rust does not have this problem.

In Rust there is no use for ‘null’ (it exists for the purposes of FFI). As Java has Optional, rust has Option. There are a few key differences though, and I’m confident that if you like Java’s Optional you’ll love Rust’s Option.

The first and most obvious is that Rust has always had Option. This means that there is no legacy ecosystem where you may run into a null value. There are no libraries or frameworks that are ‘old rust’ and dangerous to use in a type safe way. You will never have to deal with a NPE in rust because it has never existed.

The second is that Unlike Java’s approach of making Optional a Class, Rust’s Option is an enumerated type. Sum types are pretty cool, and not so common in popular languages. To explain what a sum type is, I’ll show you what one looks like in rust.

  enum Foo {
    Bar,
    Baz
  }

Pretty simple, right? If you’re used to enums in other languages, Rust’s probably doesn’t look so different - at least for now.

What we have here is an enum called Foo, which has two variants - Bar and Baz. This means that a value of type Foo can be either a Bar or a Baz.

We can determine which variant a value is by destructuring it. One way to do so is a match statement.


let f = Foo::Bar;

match f {
  Foo::Bar => println!("f was a Bar")
  Foo::Baz => println!("f was a Baz")
}

Look how nice that is! We can handle both cases separately, in a type safe way. Had I failed to handle one of the variant cases Rust would not have compiled my code. There is no way to accidentally forget to handle the missing case.

But enums are even more powerful than that. Let’s roll our own Option type to handle the case where a String value may be present or not. Note that this is not strictly identical to Rust’s std Option, as I don’t want to make use of generics. Rust’s actual Option type will work with any type, not just String.

enum OptionString {
  Present(String),
  Absent
}

What we have here is an enumerated type that represents either a Present value or the absence of a value. We ‘wrap’ the value in this enum, forcing the case of its absence to be handled.


let f = OptionString::Present("Our string");

match f {
  Present(s) => println!("Got the string {:?}", s) // prints: Got the string Ourstring
  Absent  => println!("No string provided")
}

This would be loosely equivalent to the following Java code.


Optional<String> f = Optional.of("Our string");

if (f.is_present()) {
  System.out.println(String.format("Got the string %s", f.get()));
} else {
  System.out.println("No string provided");
}

This isn’t quite as elegant, in my opinion. For two reasons.

1) We have to call ‘is_present()’ and then the unchecked ‘get()’ 2) We don’t have to explicitly handle the case of it not being present

This isn’t to knock the Optional class. It’s awesome. But I’ve run into multiple cases where I had to express my control flow as I do in the above. Maybe times I can use the combinator syntax but definitely not always.

A minor point, perhaps, but Java’s Optional is not free. Due to Java lacking ‘value types’, essentially just stack allocation, you must allocate on the heap to wrap a type in Optional. And, due to that lack of value types, you can not wrap a primitive type in an Optional - so you’re again forced to make use of the heap when it may not be necessary. These are minor annoyances, but coming from lower level languages I definitely find it irksome to be forced to use the heap in places where the stack is obviously acceptable.

Our Rust OptionString type actually has the same overhead as a check for null. There is 0 memory overhead, 0 allocation performed. Because ‘null’ (0) is not a valid address for our String type, we can use that to represent the Absent variant under the hood.

This may seem like a little thing, but Rust is full of patterns that will save you from real bugs all the time. Option is just the first example I’ve chosen.

If Java did not have null, and had the equivalent of what Rust has, I know that I would have many hours of development back and at least a few stressful ‘ugh, NPE in prod’ moments gone.



blog comments powered by Disqus

Published

28 December 2016

Categories