4

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>,
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ArekBulski
  • 4,520
  • 4
  • 39
  • 61

3 Answers3

12

Box<T> can never be NULL, therefore there is nothing to check.

Box<T> values will always be fully aligned, non-null pointers

std::box

You most likely wish to use Option to denote the absence / presence of a value:

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

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

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
6

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • That's not appropriate for _this_ question, especially considering you've already asked it in [How do I implement a heap-based stack using Option>?](https://stackoverflow.com/q/66972893/155423) – Shepmaster Apr 06 '21 at 16:52
  • 5
    In all honesty, and I mean this sincerely, based on this question and the one Shepmaster linked, my best advice to you is to slow down and read a good Rust tutorial. It seems like you're expecting Rust to be "C++ with funny syntax", but it's really not. It's an entirely different abstraction scheme, and the borrow checker has no real equivalent in any other mainstream language. Approach it like an entirely new paradigm. It'll do wonders. – Silvio Mayolo Apr 06 '21 at 16:56
  • 5
    Re ordering, prefer `Option>`. Done the other way you're dynamically allocating just so you can say you have nothing. Since Box is never null, `Option>` will use the null pointer bit pattern to store `None` for the option, for free. – GManNickG Apr 06 '21 at 17:37
  • 3
    "Rust has no concept of null." `std::ptr::null`? – GManNickG Apr 06 '21 at 17:44
2

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>>.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ritik Mishra
  • 658
  • 4
  • 15
  • 5
    Nitpick: Even if you *are* doing something weird and `unsafe`, `Box` *still* isn't allowed to be a null pointer, any more than `true` is allowed to be `false`. If you write code that attempts to create a null `Box`, the behavior is undefined, and anything can happen: including the `Box` actually *not* being null. So `unsafe` isn't really an exception to the rule so much as an additional constraint. – trent Apr 06 '21 at 21:46