I have a function f
that accepts two references, one mut
and one not mut
. I have values for f
inside a HashMap
:
use std::collections::HashMap;
fn f(a: &i32, b: &mut i32) {}
fn main() {
let mut map = HashMap::new();
map.insert("1", 1);
map.insert("2", 2);
{
let a: &i32 = map.get("1").unwrap();
println!("a: {}", a);
let b: &mut i32 = map.get_mut("2").unwrap();
println!("b: {}", b);
*b = 5;
}
println!("Results: {:?}", map)
}
This doesn't work because HashMap::get
and HashMap::get_mut
attempt to mutably borrow and immutably borrow at the same time:
error[E0502]: cannot borrow `map` as mutable because it is also borrowed as immutable
--> src/main.rs:15:27
|
12 | let a: &i32 = map.get("1").unwrap();
| --- immutable borrow occurs here
...
15 | let b: &mut i32 = map.get_mut("2").unwrap();
| ^^^ mutable borrow occurs here
...
18 | }
| - immutable borrow ends here
In my real code I'm using a large, complex structure instead of a i32
so it is not a good idea to clone it.
In fact, I'm borrowing two different things mutably/immutably, like:
struct HashMap {
a: i32,
b: i32,
}
let mut map = HashMap { a: 1, b: 2 };
let a = &map.a;
let b = &mut map.b;
Is there any way to explain to the compiler that this is actually safe code?
I see how it possible to solve in the concrete case with iter_mut
:
{
let mut a: &i32 = unsafe { mem::uninitialized() };
let mut b: &mut i32 = unsafe { mem::uninitialized() };
for (k, mut v) in &mut map {
match *k {
"1" => {
a = v;
}
"2" => {
b = v;
}
_ => {}
}
}
f(a, b);
}
But this is slow in comparison with HashMap::get/get_mut
TL;DR: You will need to change the type of
HashMap
When using a method, the compiler does not inspect the interior of a method, or perform any runtime simulation: it only bases its ownership/borrow-checking analysis on the signature of the method.
In your case, this means that:
get
will borrow the entireHashMap
for as long as the reference lives,get_mut
will mutably borrow the entireHashMap
for as long as the reference lives.And therefore, it is not possible with a
HashMap<K, V>
to obtain both a&V
and&mut V
at the same time.The work-around, therefore, is to avoid the need for a
&mut V
entirely.This can be accomplished by using
Cell
orRefCell
:HashMap
intoHashMap<K, RefCell<V>>
,get
in both cases,borrow()
to get a reference andborrow_mut()
to get a mutable reference.This will add a runtime check each time you call
borrow()
orborrow_mut()
, and will panic if you ever attempt to use them incorrectly (if the two keys are equal, unlike your expectations).As for using fields: this works because the compiler can reason about borrowing status on a per-field basis.