在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`
我理解这一点,但我无法想到一个优雅的解决方案来应对整个考验。
答案 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));
}