来自HashMap的可变借用和终生淘汰

时间:2019-05-17 07:05:58

标签: rust

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作为方法参数,因此应该将其生命周期自动分配给返回值?

1 个答案:

答案 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)还没有。