在Rust 书的第 13 章中,您实现了一个Cacher用于延迟初始化的结构,以演示闭包和函数式编程的使用。作为一种练习,他们鼓励读者尝试创建一个Cacher可以存储多个值的泛型。为此,他们建议使用Hashmap.
尝试修改 Cacher 以保存哈希映射而不是单个值。哈希映射的键将是传入的 arg 值,哈希映射的值将是对该键调用闭包的结果。value 函数不会直接查看 self.value 是否具有 Some 或 None 值,而是在哈希映射中查找 arg 并返回该值(如果存在)。如果它不存在,缓存器将调用闭包并将结果值保存在与其参数值关联的哈希映射中。
当前 Cacher 实现的第二个问题是它只接受采用 u32 类型参数并返回 u32 的闭包。例如,我们可能想要缓存采用字符串切片并返回 usize 值的闭包的结果。要解决此问题,请尝试引入更多通用参数以提高缓存器功能的灵活性。
为了解决这个练习,我使用了以下代码:
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))
}
}
Run Code Online (Sandbox Code Playgroud)
Cacher该代码可以编译并运行,但由于(self.calculation)(intensity)始终执行而不能作为正确的代码。即使条目存在。我从文档和示例中了解到,该Entry::or_insert函数仅在Entry不存在时才执行。
我知道问题的练习解决方案是否可以对 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。
Sve*_*ach 12
您的代码的问题在于,函数参数总是在调用 Rust(以及大多数命令式语言)中的函数之前求值。这意味着在or_insert()调用之前,代码将无条件调用(self.calculation)(intensity). 该or_insert()函数将在内部检查条目中是否已存在某个值,如果不存在,则仅插入作为参数传递的新值,但这仅在已调用之后发生。 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))
}
}
Run Code Online (Sandbox Code Playgroud)
实现中的一个微妙之处value()是您需要将引用存储self.calculation在单独的变量中。否则,闭包将触发 的借用,这与调用 触发self的可变借用重叠。如果您仅在外部范围中明确借用,则借用检查器足够聪明,可以发现它不与 重叠。self.valuesself.values.entry()self.calculationself.values
作为旁注,我建议使用rustfmt一致的代码格式。我还建议尽可能缩小特征范围,以避免不必要的重复。这两个建议都包含在上面的代码中。