我正在写一个游戏引擎。在引擎中,我有一个游戏状态,其中包含游戏中的实体列表。
我想在我的游戏状态update
上提供一个功能,该功能将告诉每个实体更新。每个实体都需要能够引用游戏状态才能正确更新自己。
这是我到目前为止的简化版本。
pub struct GameState {
pub entities: Vec<Entity>,
}
impl GameState {
pub fn update(&mut self) {
for mut t in self.entities.iter_mut() {
t.update(self);
}
}
}
pub struct Entity {
pub value: i64,
}
impl Entity {
pub fn update(&mut self, container: &GameState) {
self.value += container.entities.len() as i64;
}
}
fn main() {
let mut c = GameState { entities: vec![] };
c.entities.push(Entity { value: 1 });
c.entities.push(Entity { value: 2 });
c.entities.push(Entity { value: 3 });
c.update();
}
问题是借用检查员不喜欢我将游戏状态传递给实体:
error[E0502]: cannot borrow `*self` as immutable because `self.entities` is also borrowed as mutable
--> example.rs:8:22
|
7 | for mut t in self.entities.iter_mut() {
| ------------- mutable borrow occurs here
8 | t.update(self);
| ^^^^ immutable borrow occurs here
9 | }
| - mutable borrow ends here
error: aborting due to previous error
有人能给我一些关于更好地设计适合Rust更好的方法的建议吗?
谢谢!
答案 0 :(得分:3)
首先,让我们回答您没有提出的问题:为什么不允许这样做?
答案在于Rust对&
和&mut
指针的保证。保证&
指针指向不可变对象,即,当您可以使用该指针时,指针后面的对象不可能发生变异。保证&mut
指针是唯一一个指向对象的活动指针,即您可以确保在您对对象进行变异时没有人会观察或改变该对象。
现在,让我们看一下Entity::update
的签名:
impl Entity {
pub fn update(&mut self, container: &GameState) {
// ...
}
}
此方法有两个参数:&mut Entity
和&GameState
。但请坚持,我们可以通过self
获得&GameState
的另一个引用!例如,假设self
是第一个实体。如果我们这样做:
impl Entity {
pub fn update(&mut self, container: &GameState) {
let self_again = &container.entities[0];
// ...
}
}
然后self
和self_again
相互别名(即它们引用相同的东西),根据我上面提到的规则不允许这样做,因为其中一个指针是一个可变指针。 / p>
你能做些什么呢?
一种选择是在实体向量上删除实体,然后在其上调用update
,然后在调用后将其插回。这解决了别名问题,因为我们无法从游戏状态获得实体的另一个别名。但是,从向量中移除实体并重新插入它是具有线性复杂性的操作(向量需要移动所有以下项目),如果对每个实体执行此操作,则主更新循环以二次复杂度运行。您可以通过使用不同的数据结构来解决这个问题;这可以像Vec<Option<Entity>>
一样简单,只需take
来自Entity
的{{1}},但您可能希望将其包装为隐藏所有Option
的类型None
1}}值到外部代码。一个很好的结果是,当一个实体必须与其他实体交互时,它会在迭代实体向量时自动跳过,因为它不再存在!
上述的一个变体是简单地取得整个实体向量的所有权,并暂时用空的实体替换游戏状态的实体向量。
impl GameState {
pub fn update(&mut self) {
let mut entities = std::mem::replace(&mut self.entities, vec![]);
for mut t in entities.iter_mut() {
t.update(self);
}
self.entities = entities;
}
}
这有一个主要缺点:Entity::update
将无法与其他实体互动。
另一种选择是将每个实体包装在RefCell
。
use std::cell::RefCell;
pub struct GameState {
pub entities: Vec<RefCell<Entity>>,
}
impl GameState {
pub fn update(&mut self) {
for t in self.entities.iter() {
t.borrow_mut().update(self);
}
}
}
使用RefCell
,我们可以避免在self
上保留可变借款。在这里,我们可以使用iter
代替iter_mut
来迭代entities
。作为回报,我们现在需要调用borrow_mut
来获取指向RefCell
中包含的值的可变指针。
RefCell
基本上在运行时执行借用检查。这意味着您最终可以编写在运行时编译良好但恐慌的代码。例如,如果我们这样写Entity::update
:
impl Entity {
pub fn update(&mut self, container: &GameState) {
for entity in container.entities.iter() {
self.value += entity.borrow().value;
}
}
}
程序会惊慌失措:
thread 'main' panicked at 'already mutably borrowed: BorrowError', ../src/libcore/result.rs:788
这是因为我们最终在我们当前正在更新的实体上调用了borrow
,这仍然是borrow_mut
中GameState::update
调用所借用的实体。 Entity::update
没有足够的信息来了解哪个实体是self
,因此您必须使用try_borrow
或borrow_state
(从Rust 1.12开始它们都不稳定.1)或将其他数据传递给Entity::update
以避免使用此方法引起恐慌。