Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does conditional assignment of a matched mutable reference cause borrow errors?

I'm having trouble understanding why this causes an error:

#[derive(Debug)]
pub struct Node {
    next: Option<Box<Node>>,
}

pub fn print_root_or_next(root: &mut Node, try_next: bool) {
    let mut current = root;
    match &mut current.next {
        Some(node) => {
            if try_next {
                current = &mut *node;
            }
        }
        None => return,
    }

    println!("{:?}", current);
}
error[E0502]: cannot borrow `current` as immutable because it is also borrowed as mutable
  --> src/lib.rs:17:22
   |
8  |     match &mut current.next {
   |           ----------------- mutable borrow occurs here
...
17 |     println!("{:?}", current);
   |                      ^^^^^^^
   |                      |
   |                      immutable borrow occurs here
   |                      mutable borrow later used here

I can't see how there are conflicting borrows; even the error message seems to indicate that two borrows are one and the same. Is this an issue in with the borrow checker or is this example actually flawed in some way?

I am very interested in this limitation. I don't know much about the implementation, but considering that a basic if:

if try_next {
    current = &mut *current.next.as_mut().unwrap();
}

and basic match:

match &mut current.next {
    Some(node) => {
        current = &mut *node;
    }
    None => return,
}

and even inverting them:

if try_next {
    match &mut current.next {
        Some(node) => {
            current = &mut *node;
        }
        None => return,
    }
}

all work, there must be some thing that the borrow checker is or is not considering that conflicts with my understanding of why the original form doesn't work.

like image 964
kmdreko Avatar asked Nov 02 '20 02:11

kmdreko


People also ask

What is a mutable reference in Rust?

Mutable data can be mutably borrowed using &mut T . This is called a mutable reference and gives read/write access to the borrower.

What is borrowing in Rust?

What is Borrowing? When a function transfers its control over a variable/value to another function temporarily, for a while, it is called borrowing. This is achieved by passing a reference to the variable (& var_name) rather than passing the variable/value itself to the function.

How do you borrow variables in rust?

An Example of Borrowing in Rust You can borrow the ownership of a variable by referencing the owner using the ampersand (&) symbol. Without borrowing by referencing, the program would panic. It would violate the ownership rule that a value can have one owner, and two variables cannot point to the same memory location.


1 Answers

I think that the problem is that current is a conditional reborrow of itself.

Consider this simpler code that removes the condition but uses two variables:

#[derive(Debug)]
pub struct Node {
    next: Option<Box<Node>>,
}

pub fn print_root_or_next(root: &mut Node) {
    let a = root;
    let b;
    match &mut a.next {
        Some(node) => {
            b = &mut *node;
        }
        None => return,
    }

    println!("{:?}", a);
    println!("{:?}", b);
}

This fails, as you would expect with the error message:

error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
  --> src/lib.rs:16:22
   |
9  |     match &mut a.next {
   |           ----------- mutable borrow occurs here
...
16 |     println!("{:?}", a);
   |                      ^ immutable borrow occurs here
17 |     println!("{:?}", b);
   |                      - mutable borrow later used here

That is, a is mutably borrowed and that borrow is kept alive because of b. So you cannot use a while b is alive.

If you reorder the last two println! lines, it compiles without issues, because of the lexical lifetime: b is printed and then forgotten, releasing the borrow and making a available again.

Now, look at this other variant, similar to yours but without the if:

pub fn print_root_or_next(root: &mut Node) {
    let mut c = root;
    match &mut c.next {
        Some(node) => {
            c = &mut *node;
        }
        None => return,
    }
    println!("{:?}", c);
}

It compiles fine, too, because when c is reborrowed it is reassigned. From this point on, it works as the b of the previous example. And you could use that b freely, what was forbidden was using a, and that is no more available here.

Back to your code:

pub fn print_root_or_next(root: &mut Node, test: bool) {
    let mut c = root;
    match &mut c.next {
        Some(node) => {
            if test {
                c = &mut *node;
            }
        }
        None => return,
    }
    println!("{:?}", c);
}

The issue here is that when c is reborrowed, it is done conditionally, the compiler does not know which one it will be, like a or like b from the example above. So it must assume that it is both at the same time! But from the example we saw that you cannot use a while b is alive, but since they are both the same value, this value just cannot be used any longer.

When the compiler complains with this weird error message:

17 | , current);
   | ^^^^^^^
   | |
   | immutable borrow occurs here
   | mutable borrow later used here

It actually means:

17 | , current);
   | ^^^^^^^
   | |
   | immutable borrow occurs here if try_next is false
   | mutable borrow at the same time used here if try_next is true

I don't know if this is a limitation of the compiler and using this reference that conditionally reborrows itself is actually safe. Maybe it is a limitation of the borrow checked, or maybe there is some subtlety that I don't understand... I suspect that allowing this might be unsound if you include more than one condition or more than one reference.

like image 113
rodrigo Avatar answered Oct 16 '22 23:10

rodrigo