Allen Wang
Allen Wang

Reputation: 107

Why is an explicit borrow required in Rust tuple pattern matching?

I am writing a binary tree in Rust, and the borrow checker really confuses me. Here is a minimal example that reproduces the problem.

The binary tree is defined as follows:

struct NonEmptyNode;

pub struct BinaryTree {
    root: Option<NonEmptyNode>,
}

The code rejected by the borrow checker is:

// Implementation #1
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
    match (self.root, bt.root) {
        (Some(ref rt), Some(ref node)) => {
            setter(rt, node);
            true
        }
        _ => false,
    }
}

The error message is

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:10:16
   |
10 |         match (self.root, bt.root) {
   |                ^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:10:27
   |
10 |         match (self.root, bt.root) {
   |                           ^^ cannot move out of borrowed content

To make it work, the code has to be modified to:

// Implementation #2
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
    match (&self.root, &bt.root) {
        // explicit borrow
        (&Some(ref rt), &Some(ref node)) => {
            // explicit borrow
            setter(rt, node);
            true
        }
        _ => false,
    }
}

If I pattern-match one variable at a time without an explicit borrow, the borrow checker won't complain at all:

// Implementation #3
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
    match self.root {
        Some(ref rt) => match bt.root {
            // No explict borrow will be fine
            Some(ref node) => {
                // No explicit borrow will be fine
                setter(rt, node);
                true
            }
            _ => false,
        },
        _ => false,
    }
}

Why does implementation #3 not require an explicit borrow, while implementation #1 does?

Upvotes: 1

Views: 823

Answers (1)

DK.
DK.

Reputation: 59015

The key is that self.root and bt.root are "place expression"s, whilst a tuple is not. The reason #3 works is that the compiler knows how to "reach through" the intermediate expressions in order to bind to the original storage location.

Another way you could look at it: very simple expressions like self.root are special in that they look and behave like values (and have a value type), but secretly the compiler remembers how it reached that value, allowing it to go back and get a pointer to where that value was read from.

The easy way to work out if something is a "place expression" is to try assigning a value to it. If you can do expr = some_value;, then expr must be a "place expression". Incidentally, this is also why when you write &self.root, you get a pointer to where self.root is being stored, rather than a pointer to a copy of self.root.

This "place expression" business doesn't work for tuples because they don't have this property. To construct a tuple, the compiler has to actually read the values for the tuple elements and move or copy them into new storage for the tuple. This destroys any place associations the compiler might have had: those values are literally no longer where they used to be.

Finally, you might want to look at Option::as_ref, which turns &Option<T> into Option<&T>. That would let you match on (self.root.as_ref(), bt.root.as_ref()), with patterns like (Some(rt), Some(node)), which might be more convenient.

Upvotes: 4

Related Questions