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

问题描述 投票:0回答:2

我正在尝试通过在我的一个项目中使用 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 的动机有些相似,但我还需要能够访问其他密钥,而不仅仅是我想要修改的密钥。

rust borrow-checker
2个回答
2
投票

您当前的问题是可变借用和不可变借用之间的冲突,但这里还有一个更深层次的问题。这种数据结构不能适用于您想要做的事情:

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
之前创建的
Toplevel
s
— 除非您使用像 竞技场分配器这样的特殊工具。

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

一般来说,引用并不真正适合创建数据结构;相反,它们适用于暂时“借用”数据结构的部分。

在您的情况下,如果您想要拥有所有

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
 实现的内部可变性允许修改 
MyStruct
substructs
 字段,即使它是从整个数据结构的其他元素引用的。

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

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
 引用。


另一种选择,可以说更“生锈”,是

仅使用索引进行交叉引用。这有几个优点和缺点:

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

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

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);
    
© www.soinside.com 2019 - 2024. All rights reserved.