[英]How do I build a Cacher in Rust without relying on the Copy trait?
I am trying to implement a Cacher
as mentioned in Chapter 13 of the Rust book and running into trouble. 我正在尝试实现Rust书第13章中提到的
Cacher
并遇到麻烦。
My Cacher
code looks like: 我的
Cacher
代码如下所示:
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 borrow cacher
as a mutable borrow. error[E0499]: cannot borrow cacher as mutable more than once at a time
每次我生成run1,run2值时,它都试图借用cacher
作为可变借项。 I don't understand why it is borrowing at all - I thought cacher.value()
should be returning a reference to the item that is stored in the cacher
which is not a borrow. cacher.value()
应该返回到存储在该项目的引用cacher
是不是借。 error[E0597]: v does not live long enough
pointing to the v I return in the None case of value(). error[E0597]: v does not live long enough
指向在value()为None的情况下返回的v。 How do I properly move the v
into the HashMap
and give it the same lifetime as the HashMap
? v
移到HashMap
并赋予其与HashMap
相同的生存期? 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.values as mutable because it is also borrowed as immutable
in value(). error[E0502]: cannot borrow
as mutable because it is also borrowed as immutable
self.values as mutable because it is also borrowed as immutable
在value()中as mutable because it is also borrowed as immutable
self.values。 self.values.get(&k)
is an immutable borrow and self.values.insert(k,v)
is a mutable borrow - though I thought .get()
was an immutable borrow and .insert()
was a transfer of ownership. self.values.get(&k)
是不可变的借用, self.values.insert(k,v)
是可变的借用-尽管我认为.get()
是不可变的借用,而.insert()
是所有权转移。 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.
这些是更根本的错误,表明我对Rust的所有权概念有误解,但重新阅读本书的内容并不清楚我错过了什么。
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
; 首先,对于函数
value(&mut self, k: K) -> &V
的定义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
. 编译器将为您插入生存期,使其变为
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. 这意味着,
self
的生存期不会因功能而缩短,因为存在具有相同生存期的函数引用,并且生存期与作用域一样长。 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
. 因此,错误
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. 其次,调用
calculation
函数,该函数在函数value()
某些内部范围内返回值,然后返回对其的引用,这是不可能的。 You expect the reference to live longer than the the referent. 您期望参考的寿命比参考对象的寿命长。 Hence the error
error[E0597]: v does not live long enough
因此,错误
error[E0597]: v does not live long enough
The third error is a bit involved. 第三个错误涉及。 You see,
let result = self.values.get(&k);
您会看到,
let result = self.values.get(&k);
as mentioned in the first statement, causes k
to be held immutably till the end of the function. 如第一条语句所述,使
k
不变地保持到函数结束。 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)
返回的
result
将在您的函数value()
保留很长时间,这意味着您不能在相同的范围内借用(可变),从而导致错误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
. 您的
K
必须是Clone
,原因是k
将被移入函数calculation
,从而使其在insert
期间无法使用。
So with K
as a Clone
, the Cacher
implementation will be: 因此,使用
K
作为Clone
, Cacher
实现将是:
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
. if self.values.contains_key ...
块始终返回,因此,仅当if self.values.contains_key ...
为false
时,才能执行if
块之后的代码。 The tiny scope created for if
condition, will only live within the condition check, ie reference taken (and returned) for if self.values.contains_key(...
will go away with this tiny scope. 为
if
条件创建的微小作用域仅存在于条件检查中,即, if self.values.contains_key(...
将在这个微小作用域中消失,则采用此引用(并返回)。
For more please refer NLL RFC 有关更多信息,请参阅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. 正如@jmb在他的答案中提到的那样,为了使测试正常工作,
V
将需要成为Clone
( impl <... V:Clone> Cacher<T, K, V>
)以按值返回或使用诸如避免了Rc
的克隆成本。
eg. 例如。
fn value(&mut self, k: K) -> V { ..
fn value(&mut self, k: K) -> Rc<V> { ..
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 (eg 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 that Cacher::value
can return by value instead of by reference. 您需要您的值至少是
Clone
以便Cacher::value
可以按值而不是按引用返回。 You can use Rc
if your values are too expensive to clone and you are ok with all callers getting the same instance. 如果您的值太昂贵而无法克隆,并且所有调用方都获得相同的实例是可以的,则可以使用
Rc
。
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 call self.values.get (k).unwrap()
after inserting the value in the map. 与存储在
HashMap
的实例(而不是分配给您的临时实例)相对,天真的获取该实例的方法是在将值插入映射后调用self.values.get (k).unwrap()
。 In order to avoid the cost of computing twice the location of the value in the map, you can use the Entry
interface: 为了避免计算值在地图中的位置两倍的开销,可以使用
Entry
接口:
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. 我相信我对第2点的回答也可以解决这一点。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.