As part of the Rust book, I implemented a Cacher struct with generics. Cacher contains two fields: a closure calculation and a value field which is a HashMap:
use std::{collections::HashMap, hash::Hash};
struct Cacher<T, U, V>
where
T: Fn(U) -> V,
U: Eq + Hash,
{
calculation: T,
value: HashMap<U, V>,
}
impl<T, U, V> Cacher<T, U, V>
where
T: Fn(U) -> V,
U: Eq + Hash,
{
fn new(calculation: T) -> Cacher<T, U, V> {
Cacher {
calculation,
value: HashMap::new(),
}
}
fn value(&mut self, arg: U) -> &V {
self.value
.entry(arg)
.or_insert_with(|| (self.calculation)(arg))
}
}
I'm struggling with the Cacher::value method which should do the following:
If the
valueHashMapcontains a key matching the provided argument, then return the corresponding value.If the
valueHashMapdoes not contain a key matching the argument, executeCacher'scalculationclosure. Save the result of this closure in theHashMap, using the provided argument as the key.
I've tried a variety of solutions that always result in conflicts between mutable borrows and immutable borrows. I'm not sure how to resolve the issue.
error[E0502]: cannot borrow `self` as immutable because it is also borrowed as mutable
--> src/lib.rs:27:29
|
24 | fn value(&mut self, arg: U) -> &V {
| - let's call the lifetime of this reference `'1`
25 | self.value
| ----------
| |
| _________mutable borrow occurs here
| |
26 | | .entry(arg)
27 | | .or_insert_with(|| (self.calculation)(arg))
| |_____________________________^^__----__________________- returning this value requires that `self.value` is borrowed for `'1`
| | |
| | second borrow occurs due to use of `self` in closure
| immutable borrow occurs here
error[E0382]: use of moved value: `arg`
--> src/lib.rs:27:29
|
12 | impl<T, U, V> Cacher<T, U, V>
| - consider adding a `Copy` constraint to this type argument
...
24 | fn value(&mut self, arg: U) -> &V {
| --- move occurs because `arg` has type `U`, which does not implement the `Copy` trait
25 | self.value
26 | .entry(arg)
| --- value moved here
27 | .or_insert_with(|| (self.calculation)(arg))
| ^^ --- use occurs due to use in closure
| |
| value used here after move
I'm running into a compile-time error because self.value.entry().or_insert_with() is a mutable borrow, while the use of self in the closure inside of or_insert_with() triggers an immutable borrow.
I'm also running into an error in the value method because I'm passing ownership of arg into entry() and trying to use it again inside of the closure. Should I be using references in this method? If so, how?
I understand the issues from a high level, but I'm struggling trying to find a fix around it. What can I do to prevent a mix of mutable and immutable borrows when trying to read or write to the value HashMap?
Solution
The solution I settled on.
The closure signature changed to Fn(&U) -> V and the value method changed to:
fn value(&mut self, arg: U) -> &V {
match self.value.entry(arg) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => {
let v = (self.calculation)(e.key());
e.insert(v)
}
}
}