Why is this RefCell still mutably borrowed?

Hi Rust community!

I recently encountered something weird and I condensed it into the following snippet:

use std::cell::RefCell;

fn main() {
    let x_vec = RefCell::new(vec![5u32, 8u32, 2u32]);
    
    // borrow `x_vec` mutably in the while-head
    while let Some(x) = x_vec.borrow_mut().pop() {
        // borrow it again in the body. 
        // ERROR: apparently the cell was not released
        let rem_count = x_vec.borrow().len();
        println!("x = {}, remaining = {}", x, rem_count);
    } 
}

Apparently drop() for RefMut gets not executed in time before the loop body starts? pop() does not return a reference, so there should not be a need to keep a mutable reference.

1 Like

This doesn't answer your question as to why, but it does fix your problem in an ugly way:

while let Some(x) = {let mut b=x_vec.borrow_mut();b.pop()} { //...
1 Like

Thanks! I'd still like to know the answer, however this is a nice trick and I will use it for now.

I'm guessing this is related to lexical borrows, and how this also interacts with temporaries that extend the lifetime of the borrow. RefCell::borrow_mut takes &mut self but must be moving that mutable borrow into the returned RefMut; that RefMut temporary must be having its scope extended (unnecessarily). Indeed, if I write a slimmed down version of this code using my own types, Rust Playground, you'll see that "Inside loop" is printed before "Dropped" - no surprise given what you're seeing with RefCell. If you uncomment the two lines in the loop, you get a borrowck error, suggesting it understands that it's still borrowed from loop header. If you add the workaround that @jethrogb suggested, then you'll see the "Dropped" before the loop body starts - again, no surprise but just reaffirms that something's fishy.

FYI, this also fails:

use std::cell::RefCell;

fn main() {
    let x_vec = RefCell::new(vec![5u32, 8u32, 2u32]);
    
    if let Some(x) = x_vec.borrow_mut().pop() {
        if let Some(y) = x_vec.borrow_mut().pop() {  // ERROR
            println!("x = {}, y = {}", x, y);
        }
    }
    
    println!("Done");
}

So is this behaviour intentional or should this be declared as a (usability) bug? At this moment I think it is the latter, because

//...
vec_x.borrow_mut().pop();
vec_x.borrow_mut().pop();
//...

or even

    if let Some(x) = x_vec.borrow_mut().pop() {
        println!("x = {}", x);
    }
    if let Some(y) = x_vec.borrow_mut().pop() {
        println!("y = {}", y);
    }

is perfectly fine.

I recommend you open a github issue, pointing to this thread. I think it's probably a known issue, but doesn't hurt to report it (if nothing else, as possibly another example of the known problem).