首页 > 解决方案 > 尽管值已经存在,但 Entry::or_insert 仍然执行

问题描述

Rust 书的第 13 章中,您实现了一个Cacher用于延迟初始化的结构,以演示闭包和函数式编程的使用。作为练习,他们鼓励读者尝试创建一个Cacher可以存储多个值的泛型。为此,他们建议使用Hashmap.

尝试修改缓存器以保存哈希映射而不是单个值。哈希映射的键将是传入的 arg 值,哈希映射的值将是对该键调用闭包的结果。value 函数不会直接查看 self.value 是否具有 Some 或 None 值,而是会在哈希映射中查找 arg 并返回该值(如果存在)。如果它不存在,缓存器将调用闭包并将结果值保存在与其 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))
    }
}

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

标签: rust

解决方案


您的代码的问题是函数参数总是在调用 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))
    }
}

实现中的一个微妙之处value()是您需要将引用存储self.calculation在一个单独的变量中。否则,闭包将触发借用,这与调用 触发self的可变借用重叠。如果您仅在外部范围内显式借用,则借用检查器足够聪明,可以确定它不与.self.valuesself.values.entry()self.calculationself.values

作为旁注,我建议使用rustfmt一致的代码格式。我还建议尽可能缩小特征范围,以避免不必要的重复。这两个建议都包含在上面的代码中。


推荐阅读