【问题标题】:How can I mutate other elements of a HashMap when using the entry pattern?使用入口模式时如何改变 HashMap 的其他元素?
【发布时间】:2019-03-21 15:52:01
【问题描述】:

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

上述sn-p的问题是cache.entry不可变地借用cache,但我也想更新cache

【问题讨论】:

  • 如果您更改了 hashmap,entry 如何仍然有效?
  • Stargateur -- 因为entry 只是哈希映射中单个项目的视图,它与其他元素或多或少无关似乎是合理的。但这是一个有趣的点,因为改变哈希映射可能会导致冲突处理逻辑影响entry——我还没有研究它是如何实现的。
  • 我不知道任何可以实现您尝试的 hashmap 的实现。
  • 你是对的——借用检查器无法推断它们是互斥的键突变。干杯!

标签: hashmap rust borrow-checker borrowing


【解决方案1】:

首先要做的是:您的示例可以使用.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 并在 if let 分支上使用 else 以使其更清晰。

【讨论】:

  • 谢谢!我很欣赏这个建议——我最终做了类似的事情(即提前退出),并通过重构具有哈希映射的容器来拆分我的一些可变和不可变借用。
【解决方案2】:

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 持有的引用无效,违反了其他 引用规则。

Rust 阻止了您在程序中引入两种可能的内存不安全类型;就像它设计的那样。

【讨论】:

  • 谢谢!这是一个非常有用的解释。
猜你喜欢
  • 2015-01-15
  • 2017-11-09
  • 1970-01-01
  • 2016-03-11
  • 2019-02-10
  • 1970-01-01
  • 2012-09-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多