How to check if a Box is a null pointer?

Viewed 3127

I want to implement a stack using pointers or something. How can I check if a Box is a null pointer? I seen some code with Option<Box<T>> and Box<Option<T>> but I don't understand this. This is as far as I went:

struct Node {
    value: i32,
    next: Box<Node>,
}

struct Stack {
    top: Box<Node>,
}
3 Answers

You don't want null. null is an unsafe antipattern even in languages where you have to use it, and thankfully Rust rids us of the atrocity. Box<T> always contains a T, never null. Rust has no concept of null.

As you've correctly pointed out, if you want a value to be optional, you use Option<T>. Whether you do Box<Option<T>> or Option<Box<T>> really doesn't matter that much, and someone who knows a bit more about the lower-level side of things can chime in on which is more efficient.

struct Node {
    value: i32,
    next: Option<Box<Node>>,
}

struct Stack {
    top: Option<Box<Node>>,
}

The Option says "this may or may not exist" and the Box says "this value is on the heap. Now, the nice thing about Option that makes it infinitely better than null is that you have to check it. You can't forget or the compiler will complain. The typical way to do so is with match

match my_stack.top {
    None => {
        // Top of stack is not present
    }
    Some(x) => {
        // Top of stack exists, and its value is x of type Box<T>
    }
}

There are tons of helper methods on the Option type itself to deal with common patterns. Below are just a few of the most common ones I use. Note that all of these can be implemented in terms of match and are just convenience functions.

The equivalent of the following Java code

if (value == null) {
  result = null;
} else {
  result = ...;
}

is

let result = value.map(|v| ...)

Or, if the inner computation can feasibly produce None as well,

let result = value.and_then(|v| ...)

If you want to provide a default value, say zero, like

if (value == null) {
  result = 0;
} else {
  result = value;
}

Then you want

result = value.unwrap_or(0)

It's probably best to stop thinking in terms of how you would handle null and start learning Option<T> from scratch. Once you get the hang of it, it'll feel ten times safer and more ergonomic than null checks.

A Box<T> is a pointer to some location on the heap that contains some data of type T. Rust guarantees that Box<T> will never be a null pointer, i.e the address should always be valid as long as you aren't doing anything weird and unsafe.

If you need to represent a value that might not be there (e.g this node is the last node, so there is no next node), you can use the Option type like so

struct Node {
    value: i32,
    next: Option<Box<Node>>,
}

struct Stack {
    top: Option<Box<Node>>,
}

Now, with Option<Box<Node>>, Node can either have a next Node or no next node. We can check if the Option is not None like so

fn print_next_node_value(node: &Node) {
    match &node.next {
        Some(next) => println!("the next value is {}", next.value),
        None => println!("there is no next node")
    }
}

Because a Box is just a pointer to some location on the heap, it can be better to use Option<Box<T>> instead of Box<Option<T>>. This is because the second one will allocate an Option<T> on the heap, while the first one will not. Additionally, Option<Box<T>> and Box<T> are equally big (both are 8 bytes). This is because Rust knows that Box<T> can never be all zeros (i.e can never be the null pointer), so it can use the all-0's state to represent the None case of Option<Box<T>>.

Related