Rust Entry::or_insert执行,尽管值已存在

Rust Entry::or_insert执行,尽管值已存在,rust,Rust,在中,您实现了一个用于延迟初始化的Cacherstruct,以演示闭包和函数编程的使用。作为练习,他们鼓励读者尝试创建一个通用的缓存,它可以存储多个值。为此,他们建议使用Hashmap 尝试修改缓存以保存哈希映射,而不是单个值。哈希映射的键将是传入的arg值,哈希映射的值将是对该键调用闭包的结果。value函数不会直接查看self.value是否有Some或None值,而是在哈希映射中查找arg并返回值(如果存在)。如果不存在,缓存器将调用闭包并将结果值保存在与其arg值关联的哈希映射中 当前缓

在中,您实现了一个用于延迟初始化的
Cacher
struct,以演示闭包和函数编程的使用。作为练习,他们鼓励读者尝试创建一个通用的
缓存
,它可以存储多个值。为此,他们建议使用
Hashmap

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

当前缓存器实现的第二个问题是,它只接受接受接受一个类型为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))
    }
}
结构缓存器 式中T:Fn(K)->V { 计算:T, 值:HashMap, } 嵌入式缓存器 其中T:Fn(K)->V, K:std::cmp::Eq+std::hash::hash+Clone, { fn新(计算:T)->缓存{ 缓存器{ 计算, 值:HashMap::new(), } } fn值(&mut self,强度:K)->&V{ self.values.entry(intensity.clone())或_insert((self.calculation)(intensity)) } } 此代码编译并运行,但由于总是执行
(self.calculation)(intensity)
,因此不能作为适当的
缓存。即使条目存在。我从文档和示例中了解到,
Entry::or_insert
函数仅在
Entry
不存在时执行

我知道这个问题的答案,但我想知道是否有可能以我目前的方式解决这个问题


编辑:如注释中所述:
或使用
插入并不能解决问题。当尝试使用(| |(self.calculation)(intensity.clone())
进行
或插入时,我得到以下错误
错误[E0502]:无法将self借用为不可变,因为它也借用为可变的

代码的问题是,在Rust中调用函数之前,始终会对函数参数进行求值(和大多数命令式语言)。这意味着在调用
或\u insert()
之前,代码将无条件地调用
(self.calculation)(intensity)
函数将在内部检查某个值是否已存在于条目中,如果没有,则只插入它作为参数传递的新值,但只有在调用了
self.calculation
后才会发生这种情况

使用
或\u insert\u 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))
    }
}
使用std::collections::HashMap;
结构缓存器{
计算:T,
值:HashMap,
}
嵌入式缓存器
哪里
K:std::cmp::Eq+std::hash::hash+Clone,
{
fn新(计算:T)->缓存{
缓存器{
计算,
值:HashMap::new(),
}
}
fn值(&mut self,强度:K)->&V
哪里
T:Fn(K)->V,
{
let calculation=&self.calculation;
自我价值观
.entry(intensity.clone())
.或|插入|带(| |计算(强度))
}
}
value()
实现中的一个子节是,您需要在单独的变量中存储对
self.calculation
的引用。否则,闭包将触发对
self
的借用,这与调用
self.values.entry()触发的对
self.values
的可变借用重叠
。如果在外部范围内明确只借用
self.calculation
,则借用检查器足够智能,可以确定它不会与
self.values
重叠


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

TL;DR:您尝试过
或_insert_with
吗?我刚刚尝试过
或| insert_with(| |(self.calculation)(intensity.clone())
并获得以下错误:
错误[E0502]:不能借用'self'作为不可变的,因为它也是作为可变的
借用的。
或_insert
或_insert_with
之间有什么区别吗?除了前者需要一个值,后者需要一个返回值的闭包之外?嗯,这是真的,你不能这样做。你只需要存储一个refe请在前面一行中的一个单独的
let
语句中执行
calculation
let calculation=&self.calculation;
。借用检查器非常聪明,可以看到
self.calculation
self.values
如果出现在同一范围内,但如果您只是使用
self.calculation
在闭包内部,它将触发借用所有
self
,这与
self.values
@SvenMarnach重叠。非常感谢。解决方案是添加您提到的行,并使用@justinas建议使用
或插入