简体   繁体   English

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

[英]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: 在运行测试用例时,我遇到了以下问题:

  1. 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是不是借。
  2. 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(). 显然,生命周期将随着它的返回而到期,但是我只想返回对其的引用以用作value()的返回。
  3. 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作为CloneCacher实现将是:

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将需要成为Cloneimpl <... V:Clone> Cacher<T, K, V> )以按值返回或使用诸如避免了Rc的克隆成本。

eg. 例如。

fn value(&mut self, k: K) -> V  { ..
fn value(&mut self, k: K) -> Rc<V> { ..
  1. 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

  2. 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))) } 
  3. I believe my answer to point 2 also solves this point. 我相信我对第2点的回答也可以解决这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM