Reputation: 22352
I can write the following function f
and the code compiles as I would expect:
use std::collections::HashMap;
struct A {
data: HashMap<u32, B>,
}
struct B {
val: Option<u32>,
}
impl A {
fn f(&mut self, key: u32) {
let data = &self.data[&key];
match data.val {
Some(value) => panic!("{}", value),
None => self.data.remove(&key),
};
}
}
Here key
is checked out first immutably &self.data[&key]
, and then again mutably (self.data.remove(&key)
), but the compiler allows this, likely because data
is never used after the mutable checkout.
However, if I then use a RefCell
rather than just a regular ref, I get a compile time error, even though the logic is otherwise (seemingly) the same:
use std::collections::HashMap;
use std::cell::RefCell;
struct A {
data: HashMap<u32, RefCell<B>>,
}
struct B {
val: Option<u32>,
}
impl A {
fn f(&mut self, key: u32) {
let data = self.data[&key].borrow();
match data.val {
Some(value) => panic!("{}", value),
None => self.data.remove(&key),
};
}
}
The error is:
error[E0502]: cannot borrow `self.data` as mutable because it is also borrowed as immutable
--> src/main.rs:17:21
|
14 | let data = self.data[&key].borrow();
| --------- immutable borrow occurs here
...
17 | None => self.data.remove(&key),
| ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
18 | };
19 | }
| - immutable borrow might be used here, when `data` is dropped and runs the destructor for type `Ref<'_, B>`
My guess is that the compiler isn't able to determine that I finished with data
, due to it being a RefCell
and thus checked at runtime, but if that's the case, is there any way I can 'check in' the value that I borrowed, since its no longer needed? Or is my only option to let it go out of scope? (Which is fine, for this small example, but aesthetically displeasing with larger ones.)
Upvotes: 6
Views: 128
Reputation: 43852
My guess is that the compiler isn't able to determine that I finished with data
The compiler could, but it chooses not to, in order to avoid worse surprises. The specific rule here is that
Drop
code (such as std::cell::Ref
) always have that code run at the end of scope,Drop timing/ordering matters because it can have side effects, so the compiler keeps it predictable.
is there any way I can 'check in' the value that I borrowed, since its no longer needed?
Explicitly drop it. Change your match
like this and it will compile:
None => {
drop(data);
self.data.remove(&key);
}
Now the compiler sees that data
no longer exists and is no longer borrowing self.data
, so it lets you borrow self.data
exclusively afterward.
Upvotes: 10