Rust书(第2版)的第13章包含一个简单的计算缓存的示例。 Cacher
将计算函数作为构造函数参数,并将缓存结果-第一次调用后,它将不会再次调用该函数,而只是返回缓存的结果:
struct Cacher<T>
where
T: Fn(u32) -> u32,
{
calculation: T,
value: Option<u32>,
}
impl<T> Cacher<T>
where
T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
}
}
}
}
它非常有限,并且书中有一些改进建议,留给读者练习。
因此,我正在尝试使其缓存多个值。 Cacher
拥有一个HashMap
的结果值,而不只是一个值。当要求提供值时,如果它在地图(缓存)中,则将其返回。否则,请对其进行计算,将其存储在缓存中,然后将其返回。
缓存器获取引用,因为它不想拥有输入。使用它时(请参阅单元测试),我是在借书,因为缓存器拥有结果。
这是我的尝试:
use std::collections::HashMap;
struct Cacher<T>
where
T: Fn(&u32) -> u32,
{
calculation: T,
values: HashMap<u32, u32>,
}
impl<T> Cacher<T>
where
T: Fn(&u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value(&mut self, arg: u32) -> &u32 {
let values = &mut self.values;
match values.get(&arg) {
Some(v) => &v,
None => {
let v = (self.calculation)(&arg);
values.insert(arg, v);
&values.get(&arg).unwrap()
}
}
}
}
#[test]
fn call_with_different_values() {
let mut c = Cacher::new(|a| a + 1);
let v1 = c.value(1);
assert_eq!(*v1, 2);
let v2 = c.value(2);
assert_eq!(*v2, 3);
}
编译器输出:
22 | fn value(&mut self, arg: u32) -> &u32 {
| - let's call the lifetime of this reference `'1`
23 | let values = &mut self.values;
24 | match values.get(&arg) {
| ------ immutable borrow occurs here
25 | Some(v) => &v,
| -- returning this value requires that `*values` is borrowed for `'1`
...
28 | values.insert(arg, v);
| ^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
我在行self.values
上借用23
作为可变项。但是,当我尝试在下一行使用它时,出现“不可变借用”错误。 match values.get(arg)
是values
的一成不变的借词吗?借款还没有发生吗?
第25
行也有错误。根据我对lifetime elision rules的理解,第三个应该在这里应用-我们将&mut self
作为方法参数,因此应该将其生命周期自动分配给返回值?
答案 0 :(得分:1)
我认为您不会对该签名感到满意:
fn value(&mut self, arg: u32) -> &u32
在没有生命周期省略的情况下,其读取为:
fn value(&'a mut self, arg: u32) -> &'a u32
即使正确实施它,这也会带来巨大的影响。例如,只要仍然使用旧结果,就无法再次调用value
。没错,毕竟没有什么阻止函数体从缓存中删除旧值。
最好遵循@hellow的建议,并将返回类型设为u32。
另一个误解:仅仅因为您已经借用了价值,并不意味着您不能再次借用它。
现在回答您的原始问题:
编译器对您没有说谎values.get(arg)
确实是values
的一成不变的借口。技术上的解释是,该方法调用的签名为(简化)get(&self, k: &Q) -> Option<&V>
。因此,只要仍然可以引用self
,借用values
(也称为&V
)就有效。但是,&V至少对于整个函数体都必须有效(以便可以返回)。现在,您尝试在None
情况下可变地借用,这意味着&V
一开始就不存在。因此,如果编译器变得更聪明,它可能会允许您的代码运行,但目前(1.34.0)还没有。