我正在尝试实现Rust书Chapter 13中提到的Cacher
并遇到麻烦。
我的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
}
}
}
}
我对该库的测试用例如下:
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);
}
}
运行测试用例时遇到以下问题:
error[E0499]: cannot borrow cacher as mutable more than once at a time
每次我生成run1,run2值时,它都会尝试借用cacher
作为可变借项。我根本不明白为什么要借用-我认为cacher.value()
应该返回对存储在cacher
中的项目的引用,而不是借用。error[E0597]: v does not live long enough
指向v我在value()为None的情况下返回。如何正确地将v
移到HashMap
并赋予其与HashMap
相同的生存期?显然,当它返回时,生存期将到期,但是我只想返回对其的引用,以用作value()的返回。error[E0502]: cannot borrow
self.values as mutable because it is also borrowed as immutable
。 self.values.get(&k)
是不可变的借款,self.values.insert(k,v)
是可变的借款-尽管我认为.get()
是不可变的借款,而.insert()
是所有权转让。和其他一些与移动有关的错误,我应该能够分别处理。这些是更根本的错误,表明我对Rust的所有权概念有误解,但重新阅读本书的内容并不清楚我错过了什么。
答案 0 :(得分:2)
我认为这里有很多问题需要考虑:
首先,对于函数value(&mut self, k: K) -> &V
的定义;编译器将为您插入生存期,使其变为value(&'a mut self, k: K) -> &'a V
。这意味着self
的生存期不会因函数而缩短,因为存在具有相同生存期的引用离开函数,并且生存期与作用域一样长。由于它是可变参考,因此您不能再次借用它。因此出现错误error[E0499]: cannot borrow cacher as mutable more than once at a time
。
第二,调用calculation
函数,该函数返回函数value()
的某些内部范围内的值,然后返回对其的引用,这是不可能的。您期望参考的寿命比参考对象的寿命长。因此,错误error[E0597]: v does not live long enough
第三个错误涉及其中。您会看到,第一条语句中提到的let result = self.values.get(&k);
导致k
保持不变,直到函数结束。返回的result
将在您的函数value()
中有效,这意味着您不能在同一范围内借用(可变),从而导致错误
error[E0502]: cannot borrow self.values as mutable because it is also borrowed as immutable in value() self.values.get(&k)
您的K
必须是Clone
,原因是k
将被移到函数calculation
中,从而使其在insert
中不可用。
因此,将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()
}
}
此处的生存期基于分支控制流。 if self.values.contains_key ...
块总是返回,因此if
块之后的代码仅在if self.values.contains_key ...
为false
时才能执行。为if
条件创建的微小作用域将仅存在于条件检查中,即为if self.values.contains_key(...
获取(并返回)的引用将被该微小作用域所取代。
有关更多信息,请参阅NLL RFC
正如@jmb在回答中所提到的那样,为了使测试正常进行,V
必须是Clone
(impl <... V:Clone> Cacher<T, K, V>
)才能按价值返回或使用{ {1}}以避免克隆费用。
例如
Rc
答案 1 :(得分:2)
返回对值的引用与借用该值相同。由于该值归缓存器所有,因此它也隐式借用了缓存器。这是有道理的:如果您引用了缓存器中的某个值,然后销毁了该缓存器,您的引用又会怎样?还请注意,如果您修改了缓存器(例如,通过插入新元素),则可能会重新分配存储空间,这会使对存储在其中的值的任何引用无效。
您需要您的值至少为Clone
,以便Cacher::value
可以按值而不是按引用返回。如果您的值过于昂贵而无法克隆,并且所有调用方都获得相同的实例是可以的,则可以使用Rc
。
获取存储在HashMap
中的实例(而不是您为其分配的临时实例)的幼稚方式是在将值插入映射后调用self.values.get (k).unwrap()
。为了避免计算值在地图中位置的两倍的开销,可以使用Entry
界面:
pub fn value(&mut self, k: K) -> Rc<V> {
self.values.entry (&k).or_insert_with (|| Rc::new (self.calculation (k)))
}
我相信我对第2点的回答也解决了这一点。