Why RefCell can return mut reference?

Using borrow_mut of a RefCell I tried to return a reference to map value. It looks so innocent. Why do I get error?

13 |           if let Some(val) = self.map.borrow_mut().get_mut(key) {
   |           ^                  --------------------- temporary value created here

Check the code here:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=542d4107fad8710cafe6fe85ce2e65c4

The borrow_mut() method returns a value of type RefMut which is a wrapper around the mutable reference. Then the call to get_mut() will borrow from this RefMut, meaning the return value of get_mut cannot outlive the RefMut. However the RefMut is dropped at the end of the line, so the return of get_mut may not outlive the line. Even moving the call to borrow_mut() doesn't help as the reference is returned from the function.

The reason for this RefMut type is that RefCell enforces at compile time that only one mutable reference exists, and dropping your RefMut will decrement a counter inside the RefCell.

To return a mutable reference into something in a RefCell, you want to return a RefMut too, so the user can decrement that counter when they are done using it. You can replace the contents of the RefMut using RefMut::map.

pub fn get(&self, key: i32) -> RefMut<i32> {
    let borrow = self.map.borrow_mut();
    RefMut::map(borrow, |inner| {
        inner.get_mut(&key)
            .expect("invalid name")
    })
}

playground

Of course you need to be aware that the RefCell will still enforce that only one mutable reference exists! It's just enforced at runtime by panicking, for example this panics:

fn main() {
    let f = Foo{map: RefCell::new(HashMap::new())};
    f.map.borrow_mut().insert(1,1);
    f.map.borrow_mut().insert(2,2);
    
    let a = f.get(1);
    let b = f.get(2); // panic here
    
    println!("{} {}", a, b);
}

playground

4 Likes

Minor nitpick or precision: when matching (or if leting), the matched expression and "its temporaries" are only dropped at the end of the match block, "contrary" to a classic let binding where the temporaries are dropped right after the let binding (which is, by the way, the reason the dbg! macro is implemented using a match)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.