rustborrow-checker

Why I "cannot assign to X because it is borrowed" by a temporary created from X, but it works with an existing Ref?


I have a function that iterates over a linked-list data structure, but it does not compile:

use std::cell::RefCell;
use std::rc::{Rc, Weak};

struct Node {
    pub data: i32,
    pub next: Option<Rc<RefCell<Node>>>,
    pub prev: Option<Weak<RefCell<Node>>>,
}

fn print_iterate_doesnt_work(node: &Rc<RefCell<Node>>) {
    let mut current_node = node.clone();
    loop {
        current_node = {
            let current_node_ref = current_node.borrow();
            print!("{} ", current_node_ref.data);
            if current_node_ref.next.is_none() {
                return;
            }

            current_node.borrow().next.as_ref().unwrap().clone()
        }
    }
}
error[E0506]: cannot assign to `current_node` because it is borrowed
    --> src/lib.rs:13:9
     |
13   |         current_node = {
     |         ^^^^^^^^^^^^ `current_node` is assigned to here but it was already borrowed
...
20   |             current_node.borrow().next.as_ref().unwrap().clone()
     |             ---------------------
     |             |
     |             `current_node` is borrowed here
     |             a temporary with access to the borrow is created here ...
21   |         }
22   |     }
     |     - ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, Node>`
     |
     = note: the temporary is part of an expression at the end of a block;
             consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
     = note: borrow occurs due to deref coercion to `RefCell<Node>`
note: deref defined here
    --> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/rc.rs:2124:5
     |
2124 |     type Target = T;
     |     ^^^^^^^^^^^
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
     |
20   |             let x = current_node.borrow().next.as_ref().unwrap().clone(); x
     |             +++++++                                                     +++

For more information about this error, try `rustc --explain E0506`.

I understand current_node.borrow() creates temporary Ref. Why doesn't compiler see that I don't use current_node and the temporary Ref?

I also don't fully understand "and the borrow might be used here, when that temporary is dropped and runs the destructor for type Ref<'_, Node>". As a result I don't understand why I should "save the expression's value in a new local variable x and then make x be the expression at the end of the block: let x = ...; x`".

Changing the last line of the block to this makes it compile though:

current_node_ref.next.as_ref().unwrap().clone()

So what's going on here?


Solution

  • Your non working version is equivalent to this:

    fn print_iterate_doesnt_work(node: &Rc<RefCell<Node>>) {
        let mut current_node = node.clone();
        loop {
            let current_node_ref = current_node.borrow();
            print!("{} ", current_node_ref.data);
            if current_node_ref.next.is_none() { 
                return; 
            }
            
            current_node = current_node.borrow().next.as_ref().unwrap().clone();  
        }
    }
    

    Now the problem is that the temporary current_node.borrow() isn't dropped until the end of the statement. But at that time current_node has already been replaced.

    The working example works, because it stores the temporary in a block scoped variable, which is dropped before returning from the block, so also before reassigning current_node so everything works out.