[英]Rust: Modify value in HashMap while immutably borrowing the whole HashMap
I'm trying to learn Rust by using it in a project of mine.我正在尝试通过在我的一个项目中使用 Rust 来学习它。 However, I've been struggling with the borrow checker quite a bit in some code which has a very similar form to the following:但是,在某些代码中,我一直在努力使用借用检查器,这些代码的形式与以下内容非常相似:
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);
}
When compiling, I get the following message:编译时,我收到以下消息:
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
I think I understand why this happens: toplevel.my_structs.get_mut(...)
borrows toplevel.my_structs
as mutable.我想我明白为什么会发生这种情况: toplevel.my_structs.get_mut(...)
借用toplevel.my_structs
作为可变的。 However, in the same block, toplevel.my_structs.get(...)
also borrows toplevel.my_structs
(though this time as immutable).然而,在同一个块中, toplevel.my_structs.get(...)
也借用了toplevel.my_structs
(虽然这次是不可变的)。
I also see how this would indeed be a problem if the function which borrows &mut toplevel.my_structs
, say, added a new key.我还看到,如果借用&mut toplevel.my_structs
的函数添加了一个新键,这确实是一个问题。
However, all that is done here in the &mut toplevel.my_structs
borrow is modify the value corresponding to a specific key, which shouldn't change memory layout (and that's guaranteed, thanks to Pin
).然而,在&mut toplevel.my_structs
借用中所做的只是修改与特定键对应的值,这不应该改变内存布局(这是有保证的,感谢Pin
)。 Right?对?
Is there a way to communicate this to the compiler, so that I can compile this code?有没有办法将其传达给编译器,以便我可以编译此代码? This appears to be somewhat similar to what motivates the hashmap::Entry
API, but I need to be able to access other keys as well, not only the one I want to modify.这似乎有点类似于hashmap::Entry
API 的动机,但我还需要能够访问其他键,而不仅仅是我想要修改的键。
Your current problem is about conflicting mutable and immutable borrows, but there's a deeper problem here.您当前的问题是关于可变借用和不可变借用的冲突,但这里有一个更深层次的问题。 This data structure cannot work for what you're trying to do:此数据结构不能用于您要执行的操作:
struct MyStruct<'a> {
value: i32,
substructs: Option<Vec<Pin<&'a MyStruct<'a>>>>,
}
struct Toplevel<'a> {
my_structs: HashMap<String, Pin<Box<MyStruct<'a>>>>,
}
Any time a type has a lifetime parameter, that lifetime necessarily outlives (or lives exactly as long as) the values of that type.任何时候一个类型有一个生命周期参数,这个生命周期必然比该类型的值更长寿(或完全一样)。 A container Toplevel<'a>
which contains references &'a MyStruct
must refer to MyStruct
s which were created before the Toplevel
— unless you're using special tools like an arena allocator .包含引用&'a MyStruct
的容器Toplevel<'a>
必须引用在Toplevel
之前创建的MyStruct
s - 除非您使用特殊工具,如arena allocator 。
(It's possible to straightforwardly build a tree of references, but they must be constructed leaves first and not using a recursive algorithm; this is usually impractical for dynamic input data.) (直接构建引用树是可能的,但它们必须首先构造叶子而不是使用递归算法;这对于动态输入数据通常是不切实际的。)
In general, references are not really suitable for creating data structures;一般来说,引用并不真正适合创建数据结构; rather they're for temporarily “borrowing” parts of data structures.相反,它们用于临时“借用”数据结构的一部分。
In your case, if you want to have a collection of all the MyStructs
and also be able to add connections between them after they are created, you need both shared ownership and interior mutability:在您的情况下,如果您想要拥有所有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>>>,
}
The shared ownership via Rc
allows both Toplevel
and any number of MyStruct
s to refer to other MyStruct
s.通过Rc
的共享所有权允许Toplevel
和任意数量的MyStruct
s 引用其他MyStruct
s。 The interior mutability via RefCell
allows the MyStruct
's substructs
field to be modified even while it's being referred to from other elements of the overall data structure.通过RefCell
的内部可变性允许修改MyStruct
的substructs
字段,即使它被整个数据结构的其他元素引用。
Given these definitions, you can write the code that you wanted:根据这些定义,您可以编写所需的代码:
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);
}
Note that because you're having "abc"
refer to itself, this creates a reference cycle, which will not be freed when the Toplevel
is dropped.请注意,因为您有"abc"
引用自身,这会创建一个引用循环,当Toplevel
被删除时,它不会被释放。 To fix this, you can impl Drop for Toplevel
and explicitly remove all the substructs
references.要解决此问题,您可以impl Drop for Toplevel
并显式删除所有substructs
引用。
Another option, arguably more 'Rusty' is to just use indices for cross-references .另一种选择,可以说更“生锈”的是只使用索引进行交叉引用。 This has several pros and cons:这有几个优点和缺点:
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()]);
}
In your code, you are attempting to modify a value referenced in the vector as immutable, which is not allowed.在您的代码中,您试图将向量中引用的值修改为不可变的,这是不允许的。 You could store mutable references in the vector instead, and mutate them directly, like this:您可以将可变引用存储在向量中,然后直接改变它们,如下所示:
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());
However, it's easier (although more expensive) to store clones of the structs instead of references:但是,存储结构的克隆而不是引用更容易(虽然更昂贵):
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);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.