问题
I am trying to implement a Cacher
as mentioned in Chapter 13 of the Rust book and running into trouble.
My Cacher
code looks like:
use std::collections::HashMap;
use std::hash::Hash;
pub struct Cacher<T, K, V>
where
T: Fn(K) -> V,
{
calculation: T,
values: HashMap<K, V>,
}
impl<T, K: Eq + Hash, V> Cacher<T, K, V>
where
T: Fn(K) -> V,
{
pub fn new(calculation: T) -> Cacher<T, K, V> {
Cacher {
calculation,
values: HashMap::new(),
}
}
pub fn value(&mut self, k: K) -> &V {
let result = self.values.get(&k);
match result {
Some(v) => {
return v;
}
None => {
let v = (self.calculation)(k);
self.values.insert(k, v);
&v
}
}
}
}
and my test case for this lib looks like:
mod cacher;
#[cfg(test)]
mod tests {
use cacher::Cacher;
#[test]
fn repeated_runs_same() {
let mut cacher = Cacher::new(|x| x);
let run1 = cacher.value(5);
let run2 = cacher.value(7);
assert_ne!(run1, run2);
}
}
I ran into the following problems when running my test case:
error[E0499]: cannot borrow cacher as mutable more than once at a time
Each time I make a run1, run2 value it is trying to borrowcacher
as a mutable borrow. I don't understand why it is borrowing at all - I thoughtcacher.value()
should be returning a reference to the item that is stored in thecacher
which is not a borrow.error[E0597]: v does not live long enough
pointing to the v I return in the None case of value(). How do I properly move thev
into theHashMap
and give it the same lifetime as theHashMap
? Clearly the lifetime is expiring as it returns, but I want to just return a reference to it to use as the return from value().error[E0502]: cannot borrow
self.valuesas mutable because it is also borrowed as immutable
in value().self.values.get(&k)
is an immutable borrow andself.values.insert(k,v)
is a mutable borrow - though I thought.get()
was an immutable borrow and.insert()
was a transfer of ownership.
and a few other errors related to moving which I should be able to handle separately. These are much more fundamental errors that indicate I have misunderstood the idea of ownership in Rust, yet rereading the segment of the book doesn't make clear to me what I missed.
回答1:
I think there a quite a few issues to look into here:
First, for the definition of the function value(&mut self, k: K) -> &V
; the compiler will insert the lifetimes for you so that it becomes value(&'a mut self, k: K) -> &'a V
. This means, the lifetime of the self
cannot shrink for the sake of the function, because there is reference coming out of the function with the same lifetime, and will live for as long as the scope. Since it is a mutable reference, you cannot borrow it again. Hence the error error[E0499]: cannot borrow cacher as mutable more than once at a time
.
Second, you call the calculation
function that returns the value within some inner scope of the function value()
and then you return the reference to it, which is not possible. You expect the reference to live longer than the the referent. Hence the error error[E0597]: v does not live long enough
The third error is a bit involved. You see, let result = self.values.get(&k);
as mentioned in the first statement, causes k
to be held immutably till the end of the function. result
returned will live for as long your function value()
, which means you cannot take a borrow(mutable) in the same scope, giving the error
error[E0502]: cannot borrow self.values as mutable because it is also borrowed as immutable in value() self.values.get(&k)
Your K
needs to be a Clone
, reason being k
will be moved into the function calculation
, rendering it unusable during insert
.
So with K
as a Clone
, the Cacher
implementation will be:
impl<T, K: Eq + Hash + Clone, V> Cacher<T, K, V>
where
T: Fn(K) -> V,
{
pub fn new(calculation: T) -> Cacher<T, K, V> {
Cacher {
calculation,
values: hash_map::HashMap::new(),
}
}
pub fn value(&mut self, k: K) -> &V {
if self.values.contains_key(&k) {
return &self.values[&k];
}
self.values.insert(k.clone(), (self.calculation)(k.clone()));
self.values.get(&k).unwrap()
}
}
This lifetimes here are based on the branching control flow. The if self.values.contains_key ...
block always returns, hence the code after if
block can only be executed when if self.values.contains_key ...
is false
. The tiny scope created for if
condition, will only live within the condition check, i.e reference taken (and returned) for if self.values.contains_key(...
will go away with this tiny scope.
For more please refer NLL RFC
As mentioned by @jmb in his answer, for your test to work, V
will need to be a Clone
(impl <... V:Clone> Cacher<T, K, V>
) to return by value or use shared ownership like Rc
to avoid the cloning cost.
eg.
fn value(&mut self, k: K) -> V { ..
fn value(&mut self, k: K) -> Rc<V> { ..
回答2:
Returning a reference to a value is the same thing as borrowing that value. Since that value is owned by the cacher, it implicitly borrows the cacher too. This makes sense: if you take a reference to a value inside the cacher then destroy the cacher, what happens to your reference? Note also that if you modify the cacher (e.g. by inserting a new element), this could reallocate the storage, which would invalidate any references to values stored inside.
You need your values to be at least
Clone
so thatCacher::value
can return by value instead of by reference. You can use Rc if your values are too expensive to clone and you are ok with all callers getting the same instance.The naive way to get the instance that was stored in the
HashMap
as opposed to the temporary you allocated to build it would be to callself.values.get (k).unwrap()
after inserting the value in the map. In order to avoid the cost of computing twice the location of the value in the map, you can use the Entry interface:pub fn value(&mut self, k: K) -> Rc<V> { self.values.entry (&k).or_insert_with (|| Rc::new (self.calculation (k))) }
I believe my answer to point 2 also solves this point.
来源:https://stackoverflow.com/questions/53331382/how-do-i-build-a-cacher-in-rust-without-relying-on-the-copy-trait