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
value
HashMap
contains a key matching the provided argument, then return the corresponding value.If the
value
HashMap
does not contain a key matching the argument, executeCacher
'scalculation
closure. 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)
}
}
}