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:
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'?
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.
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.
(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
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.
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