如何借用 HashMap 同时进行读写?

use*_*932 3 rust

我有一个函数f接受两个引用,一个mut和一个不接受mut。我有fa 内的值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)
}
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为HashMap::getHashMap::get_mut尝试同时可变借和不变借:

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)
}
Run Code Online (Sandbox Code Playgroud)

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

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

struct HashMap {
    a: i32,
    b: i32,
}
let mut map = HashMap { a: 1, b: 2 };
let a = &map.a;
let b = &mut map.b;
Run Code Online (Sandbox Code Playgroud)

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

我看到在具体情况下如何解决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);
}
Run Code Online (Sandbox Code Playgroud)

但这与 HashMap::get/get_mut

Mat*_* M. 5

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


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

就您而言,这意味着:

  • 只要引用存在,usingget就会借用整个HashMap
  • 只要引用存在,usingget_mut就会可变地借用整个HashMap

因此,使用 a 不可能同时HashMap<K, V>获得 a&V&mut V


因此,解决方法是避免需要&mut V完全。

这可以通过使用Cell或来完成RefCell

  • 把你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);
}
Run Code Online (Sandbox Code Playgroud)

这将在您每次调用borrow()or时添加运行时检查borrow_mut(),并且如果您尝试不正确地使用它们(如果两个键相等,与您的期望不同),则会发生恐慌。


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


Ric*_*aca 5

自从提出问题以来,事情似乎发生了变化。在 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)
}
Run Code Online (Sandbox Code Playgroud)

操场

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

  • 改变的是[非词汇生命周期](https://github.com/rust-lang/rfcs/pull/2094)的引入。基本上,编译器会发现您没有同时使用“a”和“b”,因此它只是在调用“get_mut”之前删除“a”。如果您在可变地借用“b”之后使用“a”,即在打印“b”之后立即打印“a”,这仍然会失败。 (3认同)