关于hashmap:如何在不依赖复制特征的情况下在Rust中构建Cacher?

How do I build a Cacher in Rust without relying on the Copy trait?

我正在尝试实现Rust书第13章中提到的Cacher并遇到麻烦。

我的Cacher代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
            }
        }
    }
}

和我对该库的测试用例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()的返回。
  • value()中的error[E0502]: cannot borrow self.values as mutable because it is also borrowed as immutableself.values.get(&k)是不可变的借项,而self.values.insert(k,v)是可变的借项-尽管我认为.get()是不可变的借项,而.insert()是所有权转让。
  • 以及其他一些与移动有关的错误,我应该能够分别处理。这些是更根本的错误,表明我误解了Rust的所有权概念,但是重新阅读本书的内容并不清楚我错过了什么。


    我认为这里有很多问题需要探讨:

    首先,对于函数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作为CloneCacher实现将是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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 self.values.contains_key ...false时才能执行if块之后的代码。为if条件创建的微小作用域将仅存在于条件检查中,即为if self.values.contains_key(...获取(并返回)的引用将消失于该微小作用域。

    有关更多信息,请参阅NLL RFC

    如@jmb在他的回答中提到的那样,为了使测试正常进行,V必须是Clone(impl <... V:Clone> Cacher<T, K, V>)才能按值返回或使用诸如Rc的共享所有权来避免克隆费用。

    例如

    1
    2
    fn value(&mut self, k: K) -> V  { ..
    fn value(&mut self, k: K) -> Rc<V> { ..


  • 返回对值的引用与借用该值相同。由于该值归缓存器所有,因此它也隐式借用了缓存器。这是有道理的:如果您引用了缓存器中的某个值,然后销毁了该缓存器,那么对您的引用会发生什么?还请注意,如果您修改了缓存器(例如,通过插入新元素),这可能会重新分配存储空间,这将使对存储在其中的值的任何引用无效。

    您需要您的值至少为Clone,以便Cacher::value可以按值而不是按引用返回。如果您的值太昂贵而无法克隆,并且所有调用方都获得相同的实例是可以的,则可以使用Rc

  • 获取存储在HashMap中的实例(而不是分配给其构建的临时对象)的天真的方法是在将值插入映射后调用self.values.get (k).unwrap()。为了避免计算值在地图中的位置两倍的开销,可以使用Entry界面:

    1
    2
    3
    pub fn value(&mut self, k: K) -> Rc<V> {
        self.values.entry (&k).or_insert_with (|| Rc::new (self.calculation (k)))
    }
  • 我相信我对第2点的回答也可以解决这一点。