Rust:在不可变地借用整个 HashMap 的同时修改 HashMap 中的值

Rust: Modify value in HashMap while immutably borrowing the whole HashMap

我正在尝试通过在我的一个项目中使用它来学习 Rust。 但是,在一些与以下形式非常相似的代码中,我一直在努力使用借用检查器:

use std::collections::HashMap;
use std::pin::Pin;
use std::vec::Vec;

struct MyStruct<'a> {
    value: i32,
    substructs: Option<Vec<Pin<&'a MyStruct<'a>>>>,
}

struct Toplevel<'a> {
    my_structs: HashMap<String, Pin<Box<MyStruct<'a>>>>,
}

fn main() {
    let mut toplevel = Toplevel {
        my_structs: HashMap::new(),
    };

    // First pass: add the elements to the HashMap
    toplevel.my_structs.insert(
        "abc".into(),
        Pin::new(Box::new(MyStruct {
            value: 0,
            substructs: None,
        })),
    );
    toplevel.my_structs.insert(
        "def".into(),
        Pin::new(Box::new(MyStruct {
            value: 5,
            substructs: None,
        })),
    );
    toplevel.my_structs.insert(
        "ghi".into(),
        Pin::new(Box::new(MyStruct {
            value: -7,
            substructs: None,
        })),
    );

    // Second pass: for each MyStruct, add substructs
    let subs = vec![
        toplevel.my_structs.get("abc").unwrap().as_ref(),
        toplevel.my_structs.get("def").unwrap().as_ref(),
        toplevel.my_structs.get("ghi").unwrap().as_ref(),
    ];
    toplevel.my_structs.get_mut("abc").unwrap().substructs = Some(subs);
}

编译时,我收到以下消息:

error[E0502]: cannot borrow `toplevel.my_structs` as mutable because it is also borrowed as immutable
  --> src/main.rs:48:5
   |
44 |         toplevel.my_structs.get("abc").unwrap().as_ref(),
   |         ------------------- immutable borrow occurs here
...
48 |     toplevel.my_structs.get_mut("abc").unwrap().substructs = Some(subs);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^--------------------
   |     |
   |     mutable borrow occurs here
   |     immutable borrow later used here

我想我明白为什么会这样:toplevel.my_structs.get_mut(...) 借用 toplevel.my_structs 作为可变的。然而,在同一个块中,toplevel.my_structs.get(...) 也借用了 toplevel.my_structs(尽管这次是不可变的)。

我还看到,如果借用 &mut toplevel.my_structs 的函数添加了一个新密钥,这确实会成为一个问题。

然而,在 &mut toplevel.my_structs 借用中所做的只是修改对应于特定键的值,这不应该改变内存布局(而且这是有保证的,感谢 Pin) .对吗?

有没有办法将它传达给编译器,以便我可以编译这段代码?这似乎与 hashmap::Entry API 的动机有些相似,但我还需要能够访问其他密钥,而不仅仅是我要修改的密钥。

在您的代码中,您试图将向量中引用的值修改为不可变,这是不允许的。您可以改为将可变引用存储在向量中,然后直接改变它们,如下所示:

let subs = vec![
    toplevel.my_structs.get_mut("abc").unwrap(),
    toplevel.my_structs.get_mut("def").unwrap(),
    toplevel.my_structs.get_mut("ghi").unwrap(),
];
(*subs[0]).substructs = Some(subs.clone());

但是,存储结构的克隆而不是引用更容易(尽管更昂贵):

let subs = vec![
    toplevel.my_structs.get("abc").unwrap().clone(),
    toplevel.my_structs.get("def").unwrap().clone(),
    toplevel.my_structs.get("ghi").unwrap().clone(),
];
(*toplevel.my_structs.get_mut("abc").unwrap()).substructs = Some(subs);

您当前的问题是关于冲突的可变和不可变借用,但这里有一个更深层次的问题。此数据结构不能 用于您要执行的操作:

struct MyStruct<'a> {
    value: i32,
    substructs: Option<Vec<Pin<&'a MyStruct<'a>>>>,
}

struct Toplevel<'a> {
    my_structs: HashMap<String, Pin<Box<MyStruct<'a>>>>,
}

只要类型有生命周期 参数, 生命周期必然比该类型的值长(或与该类型的值一样长)。包含引用 &'a MyStruct 的容器 Toplevel<'a> 必须引用在 Toplevel 之前创建的 MyStructs — 除非您正在使用an arena allocator.

等特殊工具

(直接构建引用树是可能的,但它们必须先构建叶子,而不是使用递归算法;这对于动态输入数据通常是不切实际的。)

一般来说,引用并不真正适合创建数据结构;而是临时”借用”部分数据结构。

在你的情况下,如果你想拥有所有 MyStructs 的集合并且还能够在它们之间添加连接 创建它们之后,你需要共享所有权和内部可变性:

use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;

struct MyStruct {
    value: i32,
    substructs: Option<Vec<Rc<RefCell<MyStruct>>>>,
}

struct Toplevel {
    my_structs: HashMap<String, Rc<RefCell<MyStruct>>>,
}

通过 Rc 的共享所有权允许 Toplevel 和任意数量的 MyStruct 引用其他 MyStruct。通过 RefCell 的内部可变性允许修改 MyStructsubstructs 字段,即使它被整个数据结构的其他元素引用。

根据这些定义,您可以编写所需的代码:

fn main() {
    let mut toplevel = Toplevel {
        my_structs: HashMap::new(),
    };

    // First pass: add the elements to the HashMap
    toplevel.my_structs.insert(
        "abc".into(),
        Rc::new(RefCell::new(MyStruct {
            value: 0,
            substructs: None,
        })),
    );
    toplevel.my_structs.insert(
        "def".into(),
        Rc::new(RefCell::new(MyStruct {
            value: 5,
            substructs: None,
        })),
    );
    toplevel.my_structs.insert(
        "ghi".into(),
        Rc::new(RefCell::new(MyStruct {
            value: -7,
            substructs: None,
        })),
    );

    // Second pass: for each MyStruct, add substructs
    let subs = vec![
        toplevel.my_structs["abc"].clone(),
        toplevel.my_structs["def"].clone(),
        toplevel.my_structs["ghi"].clone(),
    ];
    toplevel.my_structs["abc"].borrow_mut().substructs = Some(subs);
}

请注意,因为您要让 "abc" 引用自身,这会创建一个引用循环,当 Toplevel 被删除时,它不会被释放。要解决此问题,您可以 impl Drop for Toplevel 并明确删除所有 substructs 引用。


另一种选择,可以说是'Rusty' 只使用索引进行交叉引用。这有几个优点和缺点:

  • 增加额外哈希查找的成本。
  • 消除引用计数和内部可变性的成本。
  • 可以有“悬空引用”:可以从映射中删除一个键,使对它的引用无效。
use std::collections::HashMap;

struct MyStruct {
    value: i32,
    substructs: Option<Vec<String>>,
}

struct Toplevel {
    my_structs: HashMap<String, MyStruct>,
}

fn main() {
    let mut toplevel = Toplevel {
        my_structs: HashMap::new(),
    };

    // First pass: add the elements to the HashMap
    toplevel.my_structs.insert(
        "abc".into(),
        MyStruct {
            value: 0,
            substructs: None,
        },
    );
    toplevel.my_structs.insert(
        "def".into(),
        MyStruct {
            value: 5,
            substructs: None,
        },
    );
    toplevel.my_structs.insert(
        "ghi".into(),
        MyStruct {
            value: -7,
            substructs: None,
        },
    );

    // Second pass: for each MyStruct, add substructs
    toplevel.my_structs.get_mut("abc").unwrap().substructs =
        Some(vec!["abc".into(), "def".into(), "ghi".into()]);
}