如何借用HashMap同时读写?

时间:2017-06-09 08:51:10

标签: rust

我有一个函数f,它接受​​两个引用,一个mut,一个不mut。我在f

中有HashMap的值
use std::collections::HashMap;

fn f(a: &i32, b: &mut i32) {}

fn main() {
    let mut map = HashMap::new();

    map.insert("1", 1);
    map.insert("2", 2);

    {
        let a: &i32 = map.get("1").unwrap();
        println!("a: {}", a);

        let b: &mut i32 = map.get_mut("2").unwrap();
        println!("b: {}", b);
        *b = 5;
    }
    println!("Results: {:?}", map)
}

这不起作用,因为HashMap::getHashMap::get_mut试图同时借入和不可避免地借入:

error[E0502]: cannot borrow `map` as mutable because it is also borrowed as immutable
  --> src/main.rs:15:27
   |
12 |         let a: &i32 = map.get("1").unwrap();
   |                       --- immutable borrow occurs here
...
15 |         let b: &mut i32 = map.get_mut("2").unwrap();
   |                           ^^^ mutable borrow occurs here
...
18 |     }
   |     - immutable borrow ends here

在我的真实代码中,我使用的是大型复杂结构而不是i32,因此克隆它并不是一个好主意。

事实上,我可以不可变地/不可变地借用两件不同的东西,例如:

struct HashMap {
    a: i32,
    b: i32,
}
let mut map = HashMap { a: 1, b: 2 };
let a = &map.a;
let b = &mut map.b;

有没有办法向编译器解释这实际上是安全的代码?

我看到如何用iter_mut来解决具体案例:

{
    let mut a: &i32 = unsafe { mem::uninitialized() };
    let mut b: &mut i32 = unsafe { mem::uninitialized() };
    for (k, mut v) in &mut map {
        match *k {
            "1" => {
                a = v;
            }
            "2" => {
                b = v;
            }
            _ => {}
        }
    }
    f(a, b);
}

但与HashMap::get/get_mut

相比,这是缓慢的

2 个答案:

答案 0 :(得分:4)

TL; DR:您需要更改HashMap的类型

使用方法时,编译器检查方法的内部,或执行任何运行时模拟:它只将其所有权/借用检查分析基于方法的签名。< / p>

在您的情况下,这意味着:

    只要引用存在,
  • 使用get将借用整个HashMap
  • 只要引用存在,
  • 使用get_mut将可变地借用整个HashMap

因此,HashMap<K, V>无法同时同时获得&V&mut V

因此,解决方法是完全避免使用&mut V

这可以通过CellRefCell

来实现
  • 将您的HashMap变为HashMap<K, RefCell<V>>
  • 在这两种情况下都使用get
  • 使用borrow()获取引用,borrow_mut()获取可变引用。
use std::{cell::RefCell, collections::HashMap};

fn main() {
    let mut map = HashMap::new();

    map.insert("1", RefCell::new(1));
    map.insert("2", RefCell::new(2));

    {
        let a = map.get("1").unwrap();
        println!("a: {}", a.borrow());

        let b = map.get("2").unwrap();
        println!("b: {}", b.borrow());
        *b.borrow_mut() = 5;
    }

    println!("Results: {:?}", map);
}

每次调用borrow()borrow_mut()时都会添加运行时检查,如果您尝试错误地使用它们(如果两个键相同,与您的期望不同),则会发生混乱。< / p>

至于使用字段:这是有效的,因为编译器可以推断每个字段的借用状态。

答案 1 :(得分:1)

自提出问题以来,某些事情似乎已经改变。在Rust 1.38.0(可能更早)中,以下代码可以编译和运行:

use std::collections::HashMap;

fn f(a: &i32, b: &mut i32) {}

fn main() {
    let mut map = HashMap::new();

    map.insert("1", 1);
    map.insert("2", 2);

    let a: &i32 = map.get("1").unwrap();
    println!("a: {}", a);

    let b: &mut i32 = map.get_mut("2").unwrap();
    println!("b: {}", b);
    *b = 5;

    println!("Results: {:?}", map)
}

playground

不需要RefCell,甚至不需要内部作用域。