Rust for Java Programmers: Nullability

Rust for Java Programmers: Nullability

A quick look at Option in Rust and null in Java.

You are writing a program in Rust, and at some point in your program, you'll try to get or use some data that won't exist. Let's use a User Profile data structure as an example. This structure will have certain data points that are required and we can guarantee will always exist in the app (like an identifier). Conversely, the user may skip out on inputting their name and this is okay. But, Rust has no null/nil like value. How do we handle this data requirement in Rust.

In place of setting data that is may be absent or optional to null, we rely on the type system and the standard library's Option type. Option is an enumeration type with two variants; Some to represent some data is present, and None to represent the absence of data.

As defined before, you have a User Profile with the following properties:

  • id - Must exist
  • name - may be skipped

Modeling this requirement in Java, let's opt to create the following POJO definition:

public class UserProfile {
    public final String id;
  String name;    
  public UserProfile(String id) {
        assert id != null : "ID is never expected to be null";
    this.id = id;
  }
    // setters & getters left to your imagination
}

Here both "name" and "id" properties are String types. To prevent later code from accidentally passing null and creating bugs further down, we inserted a nice precondition. Lastly, we leave name uninitialized; as a class field, its value will be null.

Now to model this same concept in Rust, we may create something this:

struct UserProfile {
    id: String,
    name: Option<String>,
}

impl UserProfile {
  fn new(id: String) -> UserProfile {
      UserProfile { id, name: None }
  }
}

Here, "id" is a String but we define "name" as an Option of a String. By default, there's no way to accidentally set "id" to null. Additionally, anyone writing logic depending on name will see right away they must consider what to do when this property is missing.

The biggest advantage rust has with Option over null is that we can't accidentally use null data as if it's non-null, causing the dreaded Null Reference Error. See the following:

if (name.equalsIgnoreCase("aaron") {
    return "A-A-ron"
}

Working with the optional name property in Rust will require you to use the Option methods to either: assume present data by calling .unwrap(), potentially crashing the app if you're wrong; or use a default value in case of None; or return another Option. Let's take a look at some scenarios and tools at your disposal to handle them.

Scenarios

Make a method to return a display ready version of an optional name string or give back a default.

fn mk_display_name(name: Option<String>) -> String {
  // if name is None, we'll get "No Name", 
 //otherwise, we'll get the value inside name.
    name.unwrap_or(String::from("No Name"))
}

Alternatively, we an use pattern matching:

fn mk_display_name(name: Option<String>) -> String {
    match name {
    Some(name) => name,
    _ => "No Name".to_string()
  }
}

Return early and propagate None using the question mark(?) operator. Wherever we have the ?, the function will return a None early if the value is absent. Otherwise your variable gets the unwrapped value.

fn parse_last(full_name: Option<String>) -> Option<String> {
  let full_name = full_name?; // here full_name is a String rather than Option
  let last = full_name.split_whitespace().take(2).last()?.to_owned();
  Some(last)
}

These are just some of the methods and patterns for working with Option in rust. If you wish to learn more about this, do checkout the Rust Book and the standard API documentation. Now we can go write our User Profile and give users the option to skip out on some data entry.