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

标签 rust borrow-checker

我正在尝试通过在我的一个项目中使用它来学习 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 作为可变的。然而,在同一个 block 中,toplevel.my_structs.get(...) 也借用了 toplevel.my_structs(尽管这次是不可变的)。

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

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

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

最佳答案

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

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

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

任何时候一个类型都有一个生命周期参数,这个生命周期必然比该类型的值长寿(或与该类型的值一样长)。一个容器Toplevel<'a>其中包含引用文献 &'a MyStruct必须引用MyStruct s Toplevel 之前创建的 — 除非您使用像 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 s引用其他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引用资料。


另一个可以说更“使用rust ”的选择是只使用索引进行交叉引用。这有几个优点和缺点:

  • 增加额外哈希查找的成本。
  • 消除了引用计数和内部可变性的成本。
  • 可以有“悬空引用”:可以从映射中删除键,使其引用无效。
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()]);
}

关于Rust:在不可变地借用整个 HashMap 的同时修改 HashMap 中的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68488326/

相关文章:

json - 如何从 super JSON主体中拆分数据,以便获得具有多个键和值的 HashMap ?

rust - 我可以将 must_use 应用于函数结果吗?

rust - 如何在不看代码的情况下读取生命周期错误?

rust - 如何在 Rust 中实现获取缓存或加载操作?

rust - Rust 函数中奇怪的生命周期,发生第二个可变借用

Rust 借用某些对象类型而不是其他对象类型?

loops - 是否可以明确指定循环迭代的生命周期?

recursion - 在获取递归数据结构的所有权时避免部分移动值?

rust - 如何编写一个可以读写缓存的 rust 函数?

rust - 将结构插入映射而不复制结构时,如何使用结构的成员作为其自身的键?