使用入口模式时如何改变 HashMap 的其他元素?
How can I mutate other elements of a HashMap when using the entry pattern?
我想使用 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
。
首先要做的事情是:您的示例可以使用 .or_insert_with()
方法进行简化,该方法采用闭包 returns 在该键处插入的值。
条目模式不可能解决您的问题,因为您首先在条目中可变地借用缓存,然后在匹配(或闭包)中借用缓存。您可以尝试一下,如果您使用 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
并在 if let
分支上使用 else
使它更干净一些。
如何获得工作代码,但我想更深入地了解为什么您的代码无法编译。
您提出的代码无法静态验证为内存安全。您的递归调用完全有可能尝试访问相同的索引。查看此简化代码以了解一种可能性:
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 阻止了您在程序中引入两种可能的内存不安全类型;就像它的设计目的一样。
我想使用 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
。
首先要做的事情是:您的示例可以使用 .or_insert_with()
方法进行简化,该方法采用闭包 returns 在该键处插入的值。
条目模式不可能解决您的问题,因为您首先在条目中可变地借用缓存,然后在匹配(或闭包)中借用缓存。您可以尝试一下,如果您使用 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
并在 if let
分支上使用 else
使它更干净一些。
您提出的代码无法静态验证为内存安全。您的递归调用完全有可能尝试访问相同的索引。查看此简化代码以了解一种可能性:
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 阻止了您在程序中引入两种可能的内存不安全类型;就像它的设计目的一样。