使用进入模式时,如何更改HashMap的其他元素?

时间:2018-10-17 03:29:35

标签: hashmap rust borrow-checker borrowing

我想使用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);
}

playground

以上代码段的问题是cache.entry不变地借用了cache,但我也想更新cache

2 个答案:

答案 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,以使其更加整洁。