我想使用HashMap
来缓存依赖于映射中其他条目的昂贵计算。入口模式仅提供对匹配值的可变引用,但不提供HashMap
其余部分的引用。我非常感谢您提供反馈以更好的方式解决这个(错误的)玩具示例:
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
fn compute(cache: &mut HashMap<u32, u32>, input: u32) -> u32 {
match cache.entry(input) {
Vacant(entry) => if input > 2 {
// Trivial placeholder for an expensive computation.
*entry.insert(compute(&mut cache, input - 1) +
compute(&mut cache, input - 2))
} else {
0
},
Occupied(entry) => *entry.get(),
}
}
fn main() {
let mut cache = HashMap::<u32, u32>::new();
let foo = compute(&mut cache, 12);
println!("{}", foo);
}
以上代码段的问题是cache.entry
不变地借用了cache
,但我也想更新cache
。
答案 0 :(得分:7)
hellow has shown如何获得有效的代码,但我想进一步说明为什么您的代码无法编译。
您提出的代码 无法被静态验证为内存安全。您的递归调用完全有可能尝试访问相同的索引。请查看此简化代码,以了解一种可能性:
use std::collections::{hash_map::Entry, HashMap};
fn compute(cache: &mut HashMap<u32, u32>) {
if let Entry::Vacant(_entry) = cache.entry(42) {
let _aliased_mutable_reference = cache.get_mut(&42).unwrap();
}
}
现在有两个可变引用指向相同的值,违反了the rules of references。
另外,如果内部调用使用entry
并且不存在怎么办?
use std::collections::{hash_map::Entry, HashMap};
fn compute(cache: &mut HashMap<u32, u32>) {
if let Entry::Vacant(entry1) = cache.entry(42) {
if let Entry::Vacant(entry2) = cache.entry(41) {
entry2.insert(2);
entry1.insert(1);
}
}
}
现在,当您通过entry2
将值插入到映射中时,映射可能会重新分配基础内存,从而使entry1
持有的引用无效,这违反了 other 规则参考。
Rust阻止您将两种可能的内存不安全类型引入程序。就像它的设计意图一样。
答案 1 :(得分:4)
第一件事:可以使用.or_insert_with()
方法简化您的示例,该方法采用闭包形式,该闭包将返回要在该键处插入的值。
输入模式不可能解决您的问题,因为您首先在输入中可变地借用了缓存,然后在匹配(或关闭)中可变地借入了缓存。您可以尝试一下,如果使用RefCell
(它将借用从编译时移至运行时),则会引发恐慌。
要真正解决您的问题,您必须拆分获取和插入值,如下所示:
fn compute(cache: &mut HashMap<u32, u32>, input: u32) -> u32 {
if let Some(entry) = cache.get(&input) {
return *entry;
}
let res = if input > 2 {
// Trivial placeholder for an expensive computation.
compute(cache, input - 1) + compute(cache, input - 2)
} else {
0
};
cache.insert(input, res);
res
}
(如果您在夜间使用![feature(nll)]
,则可以省略return
,并在else
分支上使用if let
,以使其更加整洁。