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
之前创建的 MyStruct
s — 除非您正在使用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
的内部可变性允许修改 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
引用。
另一种选择,可以说是'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()]);
}
我正在尝试通过在我的一个项目中使用它来学习 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
之前创建的 MyStruct
s — 除非您正在使用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
的内部可变性允许修改 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
引用。
另一种选择,可以说是'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()]);
}