Borrow two mutable values from the same HashMap

前端 未结 2 1601
时光说笑
时光说笑 2020-11-29 12:19

I have the following code:

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

fn populate_connections(
    start: i32,
    num: i32,
    conns: &mut HashMap

        
相关标签:
2条回答
  • 2020-11-29 13:00

    If you can guarantee that your two indices are different, you can use unsafe code and avoid interior mutability:

    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: std::fmt::Debug + Eq + std::hash::Hash,
    {
        unsafe {
            assert_ne!(a, b, "`a` ({:?}) must not equal `b` ({:?})", a, b);
            let a = conns.get_mut(a).unwrap() as *mut _;
            let b = conns.get_mut(b).unwrap() as *mut _;
            (&mut *a, &mut *b)
        }
    }
    

    This code tries to have an abundance of caution. An assertion enforces the fact that the two keys are distinct and we explicitly add lifetimes to the returned variables.

    You should understand the nuances of unsafe code before blindly using this solution.


    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.
      • 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.

    0 讨论(0)
  • 2020-11-29 13:13

    If you can change your datatypes and your function signature, you can use a RefCell to create interior mutability:

    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.

    Safe alternatives to RefCell

    Depending on your exact data and code needs, you can also use types like Cell or one of the atomics. These have lower memory overhead than a RefCell and only a small effect on codegen.

    In multithreaded cases, you may wish to use a Mutex or RwLock.

    0 讨论(0)
提交回复
热议问题