Lifetime of RefCell borrows

I have been trying to do level order traversal with tree nodes wrapped in Option<Rc<RefCell<_>>>. The problem was that when I try to borrow the node and clone its subtrees of type Option<Rc<_>> and push them into a deque, the compiler didn't let me do so. It says that "borrowed value does not live long enough". I understand that using .borrow() without assigning it to a variable creates a temporary variable that gets dropped at the end of that statement. But in this case, I wasn't maintaining a reference to the subtree, I cloned the subtree. Even more weird was that because it is a binary tree, I tried it first with the left subtree and it was fine, but trying to clone the right subtree under borrow broke everything.

while !q.is_empty() {
            let mut level = vec![];
            for _ in 0..q.len() {
                let node = q.pop_front().unwrap();
                let rf = node.borrow();
                level.push(rf.val);
                
                // I expect this should always work
                if let Some(left) = node.borrow().left.clone() {
                    q.push_back(left);
                }
                // this works fine
                if let Some(right) = rf.right.clone() {
                    q.push_back(right);
                }
                // but if I change it to the following, it would not type check. (which I expect should also work)
                // if let Some(right) = node.borrow().right.clone() {
                //    q.push_back(right);
                // }
            }
            res.push(level);
}

Could someone explain the difference between those two blocks (cloning subtree) of code, why one works but not the other? Thanks in advance. The entire piece of code is here for reference.

Seems like either a compiler bug or a quirk of the language definition around the scope of temporaries (places where intermediate values like node.borrow() live).

In particular, it compiles if I add a semicolon:

            for _ in 0..q.len() {
                ...
                if let Some(right) = node.borrow().right.clone() {
                 q.push_back(right);
                };
            }

There's a special "temporary lifetime extension" rule that when returning a value via an expression at the end of a block, any temporaries in it get extended to outside the block. Then you get an error because the thing borrowed from, let node = q.pop_front().unwrap(), doesn't outlive the loop body.

Adding a semicolon disqualifies the if let ... {...} from being the block's final expression. But this rule really shouldn't apply here because you can't ever actually return a value (that borrows anything) from a for, or even from an if let with no else.

This seems like a surprise that could be eliminated without any harm, and so I will file an issue about it.

4 Likes

This is definitely surprising, but the error message does provide the semicolon solution:

help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
   |
44 |             };
   |              +
2 Likes

Thank you so much! That explains everything.

I wrote a comment to the issue and it seems quite intentional as you can see here