Lifetime of Ref<'_, T> returned by RefCell

I am running some issues with RefCell that I don't really understand. Here is the code snippet.

struct TreeNode{
     data: i32,
     left: Option<Rc<RefCell<TreeNode>>>,
     right: Option<Rc<RefCell<TreeNode>>>
}
let mut root = Rc::new(RefCell::new(TreeNode::new(1)));
let mut r_n = root.borrow().left.clone();
while let Some(current) = r_n {
    root = current;
    r_n = root.borrow().left.clone();
}

It works find and could compile. But when I want to succinct it to the following. It doesn't compile.

let mut root = Rc::new(RefCell::new(TreeNode::new(1)));
while let Some(inner) = root.borrow().left.clone() {
    root = current;
}

It gives the following error. My understanding is that the borrow() method returns Ref<> which is not dropped before we try to borrow a mutable reference to root in the assignment. But I am not very clear why Ref<> is not dropped in the second case but is dropped in the first scenario. Is it related to the while let expression? Could anyone help me to understand it? I am new to Rust and trying to figure out if there is any rule that I am missing.

error[E0506]: cannot assign to `root` because it is borrowed
    --> src/ch4.rs:141:13
     |
139  |         while let Some(current) = root.borrow().left.clone() {
     |                                   -------------
     |                                   |
     |                                   borrow of `root` occurs here
     |                                   a temporary with access to the borrow is created here ...
140  |             let new = current.clone();
141  |             root = new;
     |             ^^^^ assignment to borrowed `root` occurs here
142  |         }
     |         - ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, TreeNodeWithParent>`
     |
     = note: borrow occurs due to deref coercion to `RefCell<TreeNodeWithParent>`

Temporaries[1] created in the expression part of a while let won't be dropped until the end of the loop iteration.

Rust chooses to keep the temporaries around for as long as they could possibly be referenced, rather than attempting to have more specific rules that may be harder to predict.


If you're interested in the actual rules, The Rust Reference has a section on when destructors run for temporary scopes but it doesn't directly mention this case, because while let is desugared before any of the rules are applied, which can make it sort of confusing to follow.

The relevant rule is

Apart from lifetime extension, the temporary scope of an expression is the smallest scope that contains the expression and is one of the following:

  • The entire function body.
  • A statement.
  • The body of an if, while or loop expression.
  • The else block of an if expression.
  • The condition expression of an if or while expression, or a match guard.
  • The expression for a match arm.
  • The second operand of a lazy boolean expression.

In this case the smallest scope in that list is "A statement", specifically the match block that while let desugars to.

If we desugar the while let manually it may be clearer why this is the case

while let Some(inner) = root.borrow().left.clone() {
    root = current;
}

desugars to something like

loop {
    match root.borrow().left.clone()  {
         Some(inner) => root = current,
        _ => break,
    }
}

The match statement is the smallest scope in that list in the reference, so the temporary created by root.borrow() won't be dropped until after the match block ends.


  1. values that are created as part of an expression but not assigned to a binding ↩ī¸Ž

1 Like

Thank you so much for the reply. It's very clear and useful.