Comparing rust's Option to C's null pointer

Coming from C development, I heard people say there are no null pointers in rust, and I thought that sounds good, but I wonder how they could get by with always passing a valid value. Then I would hear people say there’s Option<T> which can have None to indicate no value. This made me think:

“what advantage does this have over a null pointer?”

Null Pointer

Let’s dig into C and it’s use of null pointers. Here we have a function that returns a pointer to a cool_thing.

int * foo();

Often in C API’s, one will return a null pointer when foo() failed to derive the int *. This means a user of this API will generally need to have a guard clause similar to:

int * my_value = foo();
if(my_value == 0) {
    return SOME_ERROR;
}

return do_stuff(my_value);

A problem that crops up is in the implementation of do_stuff(). It is most likely getting a pointer to an int. Should it have a guard clause as well? We could document that do_stuff() assumes it’s int * is not null, but that requires diligence from all the users. There may also be cases where we want to pass a null pointer to do_stuff() or an equivalent function.

error_type do_stuff(int * value) {
    if(value == 0) {
        return SOME_ERROR;
    }

    // actual logic
}

Rust’s Option<T>

The Option<T> type can be thought of as a wrapper around the desired type. This wrapper contains either Some(T) or None. Coming from C it’s use seems similar handling to a null pointer, in that one often checks for None.

There are some differences that we’ll get into with code examples. In the following snippet we have a function foo() that returns an Option<&u32>. This means it returns either None or Some(&u32). The &u32 is a reference to a 32 bit unsigned integer.

fn foo() -> Option<&u32>;

The caller of foo() needs to have logic to handle the two cases of None or Some(&u32).

let thing = foo();
let result = match thing {
    None => FAIL,
    Some(item) => do_stuff(item),
};
result

In this instance the caller matches on the value returned from foo(). If thing happens to be None then it sets result to the FAIL error code. The other match statement of Some(item) may be foreign to those coming from C. This matches the pattern Some(item) where item is the value inside of Some. The item can be thought of as a local variable. One could have Some(my_name) and then the other side of the => would be do_stuff(my_name).

There are 2 important things to note over a null pointer in C:

  1. The compiler will require both of these branches to be handled, or at least acknowledged.
  2. do_stuff() takes a &u32. It does not need to worry about unwrapping the Option<>. Rust will not allow a null pointer to be present in a &u32.

Summary

Coming from a C background it’s easy to conflate rust’s Option<> type and the common use of C’s null pointers to indicate the presence of a value. It helps to understand that the rust compiler will require code to handle the None version of an Option<> and that once the value is unwrapped from an option the need to check for None does not continue to propagate down through the API.