我有一个结构体,它将id映射到索引,反之亦然。
struct IdMapping<'a> {
external_2_internal: HashMap<&'a str, usize>,
internal_2_external: HashMap<usize, String>,
}
impl<'a> IdMapping<'a> {
fn new() -> IdMapping<'a> {
IdMapping {
external_2_internal: HashMap::new(),
internal_2_external: HashMap::new(),
}
}
fn insert(&'a mut self, internal: usize, external: String) {
self.internal_2_external.insert(internal, external);
let mapped_external = self.internal_2_external.get(&internal).unwrap();
self.external_2_internal.insert(mapped_external, internal);
}
}
如果我这样使用这个结构
fn map_ids<'a>(ids: Vec<String>) -> IdMapping<'a> {
let mut mapping = IdMapping::new();
for (i, id) in ids.iter().enumerate() {
mapping.insert(i, id.clone());
}
mapping
}
我收到以下编译错误:
error[E0499]: cannot borrow `mapping` as mutable more than once at a time
--> src/lib.rs:28:9
|
24 | fn map_ids<'a>(ids: Vec<String>) -> IdMapping<'a> {
| -- lifetime `'a` defined here
...
28 | mapping.insert(i, id.clone());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `mapping` was mutably borrowed here in the previous iteration of the loop
...
31 | mapping
| ------- returning this value requires that `mapping` is borrowed for `'a`
操场上联系
为什么我不能在每次循环迭代时可变地借用它的insert
方法的映射?我应该如何实现这个用例?
问题是由于你的结构中的self引用。
让我们首先看看这在理论上是否合理(假设我们正在编写不安全的代码):
HashMap
使用平面数组(二次探测),因此HashMap中的对象在插入新元素时地址不稳定。这意味着插入internal_2_external
可能会移动内存中现有的String
s。String
将其内容存储在单独的堆分配中,堆分配保持在相同的位置。所以即使String
被移动了,引用它的&str
仍然指向有效的内存。
因此,如果在不安全代码中实现,这实际上是可以工作的。
逻辑上的声音做这些操作,类型系统无法认识到,你可以移动String
同时保持借来的有效范围。这意味着,如果您从String
中借用&str
,类型系统将阻止您对String
执行任何突变操作,包括移动它。因此,您也不能对哈希映射执行任何变异操作,从而导致错误。
我可以看到两种安全的方法来解决这个问题:
- 让
external_2_internal
保留自己的字符串副本,即使用HashMap<String, usize>
。 - 保持字符串在单独的
Vec
:
struct IdMapping {
strings: Vec<String>,
external_2_internal: HashMap<usize /* index */, usize>,
internal_2_external: HashMap<usize, usize /* index */>,
}
如果你不想改变结构体的布局,你也可以使用不安全的代码。