rust - 迭代器循环中对容器对象的可变引用

标签 rust

我正在编写一个游戏引擎。在引擎中,我有一个游戏状态,其中包含游戏中的实体列表。

我想在我的游戏状态 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 的方法?

谢谢!

最佳答案

首先,让我们回答您没有提出的问题:为什么不允许这样做?

答案在于 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];
        // ...
    }
}

然后 selfself_again互为别名(即它们指的是同一事物),根据我上面提到的规则,这是不允许的,因为其中一个指针是可变指针。

对此你能做什么?

一个选项是在调用 update 之前从实体向量中删除一个实体在上面,然后在通话后将其插回。这解决了别名问题,因为我们无法从游戏状态中获取实体的另一个别名。然而,从向量中移除实体并重新插入它是具有线性复杂度的操作(向量需要移动以下所有项目),如果你对每个实体都这样做,那么主更新循环将以二次复杂度运行。您可以通过使用不同的数据结构来解决这个问题;这可以像 Vec<Option<Entity>> 一样简单,您只需 take Entity来自每个 Option , 虽然你可能想把它包装成一个隐藏所有 None 的类型外部代码的值。一个很好的结果是,当一个实体必须与其他实体交互时,它会在迭代实体向量时自动跳过自身,因为它不再存在!

上面的一个变体是简单地获取整个实体向量的所有权,并暂时用一个空实体替换游戏状态的实体向量。

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避免使用这种方法引起 panic 。

关于rust - 迭代器循环中对容器对象的可变引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42499283/

相关文章:

function - 我想将一个向量的引用传递给一个函数,然后在那里修改它并作为向量返回

rust - 为什么不能在同一结构中存储值和对该值的引用?

rust - 我在C++'s ` std::cin >>`的Rust模拟中缺少哪个特殊情况?

web-scraping - 如何使用 scraper crate 获取元素的内部文本?

string - 如何 "interpret"转义字符串中的字符?

rust - 在dylib中调用外部函数

error-handling - 为什么当我返回一个错误类型时它没有被隐式转换?

plugins - 如何找出 rustc::middle::ty::Ty 代表什么类型?

rust - 除了在每次关闭之前克隆它之外,还有其他选择可以在多个关闭中共享 Arc 吗?

rust - 如何使用非词法生命周期对程序进行正式推理