Rc<RefCell> borrow_mut version is not working or compiling, while normal &mut reference works

In this example, I need to access self mutably multiple times in a method. When I'm doing it with self being just &mut Self everything works great. But I need it to be Rc<RefCell> and I can't find a way to make it work.

use std::{cell::RefCell, collections::HashMap, rc::Rc};

#[derive(Debug, Clone, Copy)]
struct Bar(i32);

impl Bar {
    fn new(counter: &mut i32) -> Bar {
        *counter += 1;
        Bar(*counter)
    }
}

#[derive(Debug)]
struct Foo {
    counter: i32,
    map: HashMap<i32, Bar>,
    vec: Vec<Bar>,
}

impl Foo {
    fn new() -> Foo {
        Foo {
            counter: 0,
            map: HashMap::new(),
            vec: Vec::new(),
        }
    }

    // this works as expected
    fn get(self: &mut Self, key: i32) -> &Bar {
        self.map.entry(key).or_insert_with(|| {
            self.vec
                .pop()
                .unwrap_or_else(|| Bar::new(&mut self.counter))
        })
    }

    // // not compiling: 'cannot borrow `me` as mutable more than once at a time'
    // fn get_rc_refcell(me: &Rc<RefCell<Self>>, key: i32, counter: &mut i32) -> Bar {
    //     let mut me = me.borrow_mut();
    //     *me.map
    //         .entry(key)
    //         .or_insert_with(|| me.vec.pop().unwrap_or_else(|| Bar::new(&mut me.counter)))
    // }

    // panics: 'already borrowed: BorrowMutError',
    fn get_rc_refcell(me: &Rc<RefCell<Self>>, key: i32, counter: &mut i32) -> Bar {
        *me.borrow_mut().map.entry(key).or_insert_with(|| {
            me.borrow_mut()
                .vec
                .pop()
                .unwrap_or_else(|| Bar::new(&mut me.borrow_mut().counter))
        })
    }
}

fn main() {
    let mut counter = 0;
    let mut foo = Rc::new(RefCell::new(Foo::new()));
    Foo::get_rc_refcell(&foo, 10, &mut counter);
    dbg!(&foo);
    Foo::get_rc_refcell(&foo, 10, &mut counter);
    dbg!(&foo);
    Foo::get_rc_refcell(&foo, 11, &mut counter);
    dbg!(&foo);
    foo.borrow_mut().vec.push(Bar::new(&mut counter));
    dbg!(&foo);
    Foo::get_rc_refcell(&foo, 12, &mut counter);
    dbg!(&foo);
}

Actually, while experimenting with this I've found a solution:

    fn get_rc_refcell(me: &Rc<RefCell<Self>>, key: i32, counter: &mut i32) -> Bar {
        let me = &mut *me.borrow_mut();
        *me.map.entry(key).or_insert_with(|| {
            me.vec
                .pop()
                .unwrap_or_else(|| Bar::new(&mut me.counter))
        })
    }

but I still have questions:

  1. Why was it not working in the previous example? I understand why it was panicking, but what was wrong with 'cannot borrow me as mutable more than once at a time'?
  2. How this solution actually works? From my understanding, RefMut returned from borrow_mut should be dropped before the next line, because it's an anonymous variable. It looks like I have a 'mut reference to a variable that I do not have in the scope, which is not allowed.
  3. Also, you may have noticed that I return Bar instead of a reference to a Bar from rc_refcell_ version of the function. Is that possible to return a reference to Bar even if it would be some struct like the one returned by RefCell::borrow?

In the first version, me is a RefMut<'_, Bar> smart pointer. When you mutably borrow me.map in the function and me.vec and me.counter in the closure, they all try to mutably borrow the RefMut at the same time, resulting in an error. In your solution, by turning me into an ordinary &mut Bar reference, the compiler lets you split the borrow into me.map, me.vec, and me.counter simultaneously.

By writing let me = &mut *me.borrow_mut();, where the &mut reference is directly bound with let, you subject the RefMut to temporary lifetime extension. Instead of being dropped at the end of the statement, it will be dropped at the end of the enclosing block, or whenever the borrow checker requires it to be dropped.

This can be done with RefMut::map():

fn get_borrow_mut(me: &RefCell<Self>, key: i32) -> RefMut<'_, Bar> {
    RefMut::map(me.borrow_mut(), |me| {
        me.map
            .entry(key)
            .or_insert_with(|| me.vec.pop().unwrap_or_else(|| Bar::new(&mut me.counter)))
    })
}

(Accepting an &Rc<RefCell<Self>> would also work, but it would be redundant, since you aren't cloning the Rc in this function.)


As an aside, stacking combinators like or_insert_with and unwrap_or_else within each other can often make code difficult to follow. It can sometimes be more straightforward (if slightly more verbose) to use the equivalent match or if let expressions. But this is just a matter of personal style.

An equivalent version of get_borrow_mut using match expressions
fn get_borrow_mut(me: &RefCell<Self>, key: i32) -> RefMut<'_, Bar> {
    RefMut::map(me.borrow_mut(), |me| match me.map.entry(key) {
        Entry::Occupied(entry) => entry.into_mut(),
        Entry::Vacant(entry) => entry.insert(match me.vec.pop() {
            Some(value) => value,
            None => Bar::new(&mut me.counter),
        }),
    })
}
1 Like

Perfect answer! Thank you very much!

I still have to read about the temporary lifetime extension.

Also, when reading about the RefMut::map function, I found the map_split function, which allows split RefMut into multiple RefMuts. In my example, it's not the best solution, but it's great to know it exists. Although I did not find a way to split into more than two RefMuts with this function.

    fn get_rc_refcell(me: &Rc<RefCell<Self>>, key: i32, counter: &mut i32) -> Bar {
        let mut me = me.borrow_mut();
        let (mut map, mut vec) = RefMut::map_split(me, |me| (&mut me.map, &mut me.vec));

        *map.entry(key)
            .or_insert_with(|| vec.pop().unwrap_or_else(|| Bar::new(&mut 0)))
    }

Yeah, unfortunately there's no way to do that in safe code, short of breaking up Foo into different parts so you can call RefMut::split() multiple times. It's a weakness common to a lot of mutable smart pointer types, that they only allow you to split up a borrow into so many parts.

How would I call RefMut::split multiple times? I've tried but did not find a proper way to do it.

That's what I've tried:

		let (mut map, mut me) = RefMut::map_split(me.borrow_mut(), |me| (&mut me.map, &mut me));
		let (mut vec, mut counter) = RefMut::map_split(me, |me| (&mut me.vec, &mut me.counter));

But it does not compile:

error: lifetime may not live long enough
  --> src/main.rs:31:67
   |
31 |         let (mut map, mut me) = RefMut::map_split(me.borrow_mut(), |me| (&mut me.map, &mut me));
   |                                                                     --- ^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |                                                                     | |
   |                                                                     | return type of closure is (&mut HashMap<i32, Bar>, &mut &'2 mut Foo)
   |                                                                     has type `&'1 mut Foo`
   |
   = note: requirement occurs because of a mutable reference to `HashMap<i32, Bar>`
   = note: mutable references are invariant over their type parameter
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

That first line would give you a way to create aliasing &muts (instant UB) if it compiled.

Like @LegionMammal978 said, you have to break up Foo into different parts. Then you can create a binary tree of remapped RefMut, basically.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.