简体   繁体   English

从同一个 HashMap 中借用两个可变值

[英]Borrow two mutable values from the same HashMap

I have the following code:我有以下代码:

use std::collections::{HashMap, HashSet};

fn populate_connections(
    start: i32,
    num: i32,
    conns: &mut HashMap<i32, HashSet<i32>>,
    ancs: &mut HashSet<i32>,
) {
    let mut orig_conns = conns.get_mut(&start).unwrap();
    let pipes = conns.get(&num).unwrap();

    for pipe in pipes.iter() {
        if !ancs.contains(pipe) && !orig_conns.contains(pipe) {
            ancs.insert(*pipe);
            orig_conns.insert(*pipe);
            populate_connections(start, num, conns, ancs);
        }
    }
}

fn main() {}

The logic is not very important, I'm trying to create a function which will itself and walk over pipes.逻辑不是很重要,我正在尝试创建一个可以自行遍历管道的函数。

My issue is that this doesn't compile:我的问题是这不能编译:

error[E0502]: cannot borrow `*conns` as immutable because it is also borrowed as mutable
  --> src/main.rs:10:17
   |
9  |     let mut orig_conns = conns.get_mut(&start).unwrap();
   |                          ----- mutable borrow occurs here
10 |     let pipes = conns.get(&num).unwrap();
   |                 ^^^^^ immutable borrow occurs here
...
19 | }
   | - mutable borrow ends here

error[E0499]: cannot borrow `*conns` as mutable more than once at a time
  --> src/main.rs:16:46
   |
9  |     let mut orig_conns = conns.get_mut(&start).unwrap();
   |                          ----- first mutable borrow occurs here
...
16 |             populate_connections(start, num, conns, ancs);
   |                                              ^^^^^ second mutable borrow occurs here
...
19 | }
   | - first borrow ends here

I don't know how to make it work.我不知道如何使它工作。 At the beginning, I'm trying to get two HashSet s stored in a HashMap ( orig_conns and pipes ).一开始,我试图将两个HashSet存储在HashMaporig_connspipes )中。

Rust won't let me have both mutable and immutable variables at the same time. Rust 不会让我同时拥有可变和不可变变量。 I'm confused a bit because this will be completely different objects but I guess if &start == &num , then I would have two different references to the same object (one mutable, one immutable).我有点困惑,因为这将是完全不同的对象,但我猜如果&start == &num ,那么我将对同一个对象有两个不同的引用(一个可变的,一个不可变的)。

Thats ok, but then how can I achieve this?没关系,但是我该如何实现呢? I want to iterate over one HashSet and read and modify other one.我想遍历一个HashSet并读取和修改另一个。 Let's assume that they won't be the same HashSet .让我们假设它们不会是相同的HashSet

If you can change your datatypes and your function signature, you can use a RefCell to create interior mutability :如果您可以更改数据类型和函数签名,则可以使用RefCell创建内部可变性

use std::cell::RefCell;
use std::collections::{HashMap, HashSet};

fn populate_connections(
    start: i32,
    num: i32,
    conns: &HashMap<i32, RefCell<HashSet<i32>>>,
    ancs: &mut HashSet<i32>,
) {
    let mut orig_conns = conns.get(&start).unwrap().borrow_mut();
    let pipes = conns.get(&num).unwrap().borrow();

    for pipe in pipes.iter() {
        if !ancs.contains(pipe) && !orig_conns.contains(pipe) {
            ancs.insert(*pipe);
            orig_conns.insert(*pipe);
            populate_connections(start, num, conns, ancs);
        }
    }
}

fn main() {}

Note that if start == num , the thread will panic because this is an attempt to have both mutable and immutable access to the same HashSet .请注意,如果start == num ,线程将恐慌,因为这是对同一HashSet的可变和不可变访问的尝试。

Safe alternatives to RefCell RefCell的安全替代品

Depending on your exact data and code needs, you can also use types like Cell or one of the atomics .根据您的确切数据和代码需求,您还可以使用Cell或其中一种atomics等类型。 These have lower memory overhead than a RefCell and only a small effect on codegen.与 RefCell 相比,它们的内存开销更低,并且对RefCell的影响很小。

In multithreaded cases, you may wish to use a Mutex or RwLock .在多线程情况下,您可能希望使用MutexRwLock

Use hashbrown::HashMap使用hashbrown::HashMap

If you can switch to using hashbrown, you may be able to use a method like get_many_mut :如果您可以切换到使用 hashbrown,则可以使用get_many_mut之类的方法:

use hashbrown::HashMap; // 0.12.1

fn main() {
    let mut map = HashMap::new();
    map.insert(1, true);
    map.insert(2, false);

    dbg!(&map);

    if let Some([a, b]) = map.get_many_mut([&1, &2]) {
        std::mem::swap(a, b);
    }

    dbg!(&map);
}

Unsafe code不安全的代码

If you can guarantee that your two indices are different, you can use unsafe code and avoid interior mutability:如果你能保证你的两个索引是不同的,你可以使用不安全的代码并避免内部可变性:

use std::collections::HashMap;

fn get_mut_pair<'a, K, V>(conns: &'a mut HashMap<K, V>, a: &K, b: &K) -> (&'a mut V, &'a mut V)
where
    K: Eq + std::hash::Hash,
{
    unsafe {
        let a = conns.get_mut(a).unwrap() as *mut _;
        let b = conns.get_mut(b).unwrap() as *mut _;
        assert_ne!(a, b, "The two keys must not resolve to the same value");
        (&mut *a, &mut *b)
    }
}

fn main() {
    let mut map = HashMap::new();
    map.insert(1, true);
    map.insert(2, false);

    dbg!(&map);

    let (a, b) = get_mut_pair(&mut map, &1, &2);
    std::mem::swap(a, b);

    dbg!(&map);
}

Similar code can be found in libraries like multi_mut .类似的代码可以在multi_mut等库中找到。

This code tries to have an abundance of caution.这段代码试图非常谨慎。 An assertion enforces that the two values are distinct pointers before converting them back into mutable references and we explicitly add lifetimes to the returned variables.断言在将它们转换回可变引用之前强制这两个值是不同的指针,并且我们显式地将生命周期添加到返回的变量中。

You should understand the nuances of unsafe code before blindly using this solution.在盲目使用此解决方案之前,您应该了解不安全代码的细微差别。 Notably, previous versions of this answer were incorrect .值得注意的是,此答​​案的先前版本是不正确的 Thanks to @oberien for finding the unsoundness in the original implementation of this and proposing a fix.感谢@oberien 在最初的实现中发现了不合理之处并提出了修复建议。 This playground demonstrates how purely safe Rust code could cause the old code to result in memory unsafety. 这个游乐场演示了纯安全的 Rust 代码如何导致旧代码导致内存不安全。

An enhanced version of this solution could accept an array of keys and return an array of values:此解决方案的增强版本可以接受键数组并返回值数组:

fn get_mut_pair<'a, K, V, const N: usize>(conns: &'a mut HashMap<K, V>, mut ks: [&K; N]) -> [&'a mut V; N]

It becomes more difficult to ensure that all the incoming keys are unique, however.然而,确保所有传入的密钥都是唯一的变得更加困难。


Note that this function doesn't attempt to solve the original problem, which is vastly more complex than verifying that two indices are disjoint.请注意,此函数不会尝试解决原始问题,这比验证两个索引是否不相交要复杂得多。 The original problem requires:原来的问题需要:

  • tracking three disjoint borrows, two of which are mutable and one that is immutable.跟踪三个不相交的借用,其中两个是可变的,一个是不可变的。
  • tracking the recursive call跟踪递归调用
    • must not modify the HashMap in any way which would cause resizing, which would invalidate any of the existing references from a previous level.不得以任何会导致调整大小的方式修改HashMap ,这会使之前级别的任何现有引用无效。
    • must not alias any of the references from a previous level.不得为先前级别的任何引用设置别名。

Using something like RefCell is a much simpler way to ensure you do not trigger memory unsafety.使用像RefCell这样的东西是一种简单的方法来确保您不会触发内存不安全。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM