实现通用缓存结构时,“预期类型参数,找到对类型参数的引用”

时间:2017-09-29 15:29:21

标签: caching rust closures

the Closures chapter of the second edition of The Rust Programming Language中,作者实现了一个Cache结构,并留下了一些问题供读者修复,例如:

  • 接受通用参数并在闭包函数上返回值
  • 允许缓存多个值

我试图解决这些问题,但我很困难,无法使其发挥作用。

use std::collections::HashMap;
use std::hash::Hash;

struct Cacher<T, X, Y>
where
    T: Fn(&X) -> &Y,
    X: Eq + Hash,
{
    calculation: T,
    results: HashMap<X, Y>,
}

impl<T, X, Y> Cacher<T, X, Y>
where
    T: Fn(&X) -> &Y,
    X: Eq + Hash,
{
    fn new(calculation: T) -> Cacher<T, X, Y> {
        Cacher {
            calculation,
            results: HashMap::new(),
        }
    }

    fn value<'a>(&'a mut self, arg: &'a X) -> &'a Y {
        match self.results.get(arg) {
            Some(v) => v,
            None => {
                let res = (self.calculation)(arg);
                self.results.insert(*arg, res);
                res
            }
        }
    }
}

其中T是闭包函数类型,X是参数类型,Y是返回值类型。

我得到的错误:

error[E0308]: mismatched types
  --> src/main.rs:30:43
   |
30 |                 self.results.insert(*arg, res);
   |                                           ^^^ expected type parameter, found &Y
   |
   = note: expected type `Y`
              found type `&Y`

我理解这一点,但我无法想到一个优雅的解决方案来应对整个考验。

1 个答案:

答案 0 :(得分:2)

你已经声明你的闭包会返回一个引用:

T: Fn(&X) -> &Y,

但是你试图存储一些不是参考的东西:

results: HashMap<X, Y>,

这基本上是不相容的;你需要统一类型。

在许多情况下,没有理由将引用添加到泛型类型,因为泛型类型已经 引用。此外,强制闭包返回引用意味着像|_| 42这样的闭包无效。因此,我会说你应该返回并存储值类型。

接下来,您需要将类似的逻辑应用于value,因为它需要通过值获取参数以便存储它。另外,从elision做正确的事情中删除它的所有生命周期:fn value(&mut self, arg: X) -> &Y

一旦你理顺了这一点,就应用How to lookup from and insert into a HashMap efficiently?的知识:

fn value(&mut self, arg: X) -> &Y {
    match self.results.entry(arg) {
        Entry::Occupied(e) => e.into_mut(),
        Entry::Vacant(e) => {
            let res = (self.calculation)(e.key());
            e.insert(res)
        }
    }
}

通过一些测试证明它只被调用一次,然后你就可以了。请注意,我们必须一路做出决定,但它们并不是我们唯一可以选择的。例如,我们可以这样做,以便在返回时克隆缓存的值。

use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::hash::Hash;

struct Cacher<F, I, O>
where
    F: Fn(&I) -> O,
    I: Eq + Hash,
{
    calculation: F,
    results: HashMap<I, O>,
}

impl<F, I, O> Cacher<F, I, O>
where
    F: Fn(&I) -> O,
    I: Eq + Hash,
{
    fn new(calculation: F) -> Self {
        Cacher {
            calculation,
            results: HashMap::new(),
        }
    }

    fn value(&mut self, arg: I) -> &O {
        match self.results.entry(arg) {
            Entry::Occupied(e) => e.into_mut(),
            Entry::Vacant(e) => {
                let res = (self.calculation)(e.key());
                e.insert(res)
            }
        }
    }
}

#[test]
fn called_once() {
    use std::sync::atomic::{AtomicUsize, Ordering};
    let calls = AtomicUsize::new(0);

    let mut c = Cacher::new(|&()| {
        calls.fetch_add(1, Ordering::SeqCst);
        ()
    });

    c.value(());
    c.value(());
    c.value(());

    assert_eq!(1, calls.load(Ordering::SeqCst));
}