在chapter 13 of the Rust book中,您实现了Cacher
结构用于延迟初始化,以演示闭包和函数式编程的用法。作为一种练习,他们鼓励读者尝试创建通用的Cacher
,它可以存储多个值。为此,他们建议使用Hashmap
。
尝试修改Cacher以保留哈希图,而不是单个值。哈希图的键将是传入的arg值,哈希图的值将是对该键调用闭包的结果。而不是查看self.value是否直接具有Some或None值,value函数将在哈希图中查找arg并返回该值(如果存在)。如果不存在,Cacher会调用该闭包,并将结果值保存在与其arg值关联的哈希图中。
当前Cacher实现的第二个问题是,它仅接受采用u32类型的一个参数并返回u32的闭包。例如,我们可能想要缓存采用字符串切片并返回usize值的闭包结果。要解决此问题,请尝试引入更多通用参数以增加Cacher功能的灵活性。
为解决此问题,我使用了以下代码:
struct Cacher<T, K, V>
where T: Fn(K) -> V
{
calculation: T,
values: HashMap<K, V>,
}
impl<T, K, V> Cacher<T, K, V>
where T: Fn(K) -> V,
K: std::cmp::Eq + std::hash::Hash + Clone,
{
fn new(calculation: T) -> Cacher<T, K, V> {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value(&mut self, intensity: K) -> &V {
self.values.entry(intensity.clone()).or_insert((self.calculation)(intensity))
}
}
该代码可以编译并运行,但由于始终执行Cacher
的事实,因此不能用作正确的(self.calculation)(intensity)
。即使该条目存在。我从文档和示例中了解到,Entry::or_insert
函数仅在Entry
不存在时才执行。
我知道问题Is it possible to use a single generic for both key and value of a HashMap?的解决方案,但是我想知道是否有可能以目前的方式解决问题。
编辑:如注释中所述:or_insert_with
无法解决问题。尝试or_insert_with(|| (self.calculation)(intensity.clone()))
时,出现以下错误error[E0502]: cannot borrow self as immutable because it is also borrowed as mutable
。
答案 0 :(得分:7)
您的代码的问题在于,总是在用Rust(以及大多数命令式语言)调用函数之前评估函数参数。这意味着甚至在调用or_insert()
之前,代码将无条件调用(self.calculation)(intensity)
。 or_insert()
函数将在内部检查条目中是否已经存在一个值,并且仅插入一个新值(如果不存在则作为参数传递),但这仅在之后 {{ 1}}已被调用。
可以使用self.calculation
方法解决此问题。此方法接受闭包而不是值,并且仅在需要插入值时才调用闭包。这是完整的代码:
or_insert_with()
实现use std::collections::HashMap;
struct Cacher<T, K, V> {
calculation: T,
values: HashMap<K, V>,
}
impl<T, K, V> Cacher<T, K, V>
where
K: std::cmp::Eq + std::hash::Hash + Clone,
{
fn new(calculation: T) -> Cacher<T, K, V> {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value(&mut self, intensity: K) -> &V
where
T: Fn(K) -> V,
{
let calculation = &self.calculation;
self.values
.entry(intensity.clone())
.or_insert_with(|| calculation(intensity))
}
}
的一个妙处是,您需要将对value()
的引用存储在单独的变量中。否则,闭包将触发借用self.calculation
,这与调用self
触发的可变借用self.values
重叠。如果您仅在外部范围中显式借用self.values.entry()
,则借用检查器非常聪明,可以确定它与self.calculation
不重叠。
作为旁注,我建议使用self.values
来实现一致的代码格式。我还建议尽可能缩小特征范围的范围,以避免不必要的重复。这两个建议都包含在上面的代码中。