在迭代器循环中对容器对象的可变引用 [英] Mutable reference to container object within iterator loop

查看:36
本文介绍了在迭代器循环中对容器对象的可变引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm writing a game engine. In the engine, I've got a game state which contains the list of entities in the game.

我想在我的游戏状态 update 上提供一个函数,它会依次告诉每个实体进行更新.每个实体都需要能够引用游戏状态才能正确更新自身.

I want to provide a function on my gamestate update which will in turn tell each entity to update. Each entity needs to be able to refer to the gamestate in order to correctly update itself.

这是我目前所拥有的简化版本.

Here's a simplified version of what I have so far.

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();
}

问题是借用检查器不喜欢我将游戏状态传递给实体:

The problem is the borrow checker doesn't like me passing the gamestate to the entity:

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 的建议?

Can anyone give me some suggestions on better ways to design this that fits with Rust better?

谢谢!

推荐答案

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

First, let's answer the question you didn't ask: Why is this not allowed?

答案在于 Rust 对 &&mut 指针的保证.& 指针保证指向一个 immutable 对象,即指针后面的对象不可能发生变化,而您可以使用该指针.&mut 指针保证是指向对象的唯一活动指针,也就是说,您可以确保在更改对象时没有人会观察或更改该对象.

The answer lies around the guarantees that Rust makes about & and &mut pointers. A & pointer is guaranteed to point to an immutable object, i.e. it's impossible for the objects behind the pointer to mutate while you can use that pointer. A &mut pointer is guaranteed to be the only active pointer to an object, i.e. you can be sure that nobody is going to observe or mutate the object while you're mutating it.

现在,让我们看看Entity::update的签名:

Now, let's look at the signature of Entity::update:

impl Entity {
    pub fn update(&mut self, container: &GameState) {
        // ...
    }
}

这个方法有两个参数:一个 &mut Entity 和一个 &GameState.但是等等,我们可以通过 &GameState 获得另一个对 self 的引用!例如,假设 self 是第一个实体.如果我们这样做:

This method takes two parameters: a &mut Entity and a &GameState. But hold on, we can get another reference to self through the &GameState! For example, suppose that self is the first entity. If we do this:

impl Entity {
    pub fn update(&mut self, container: &GameState) {
        let self_again = &container.entities[0];
        // ...
    }
}

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

then self and self_again alias each other (i.e. they refer to the same thing), which is not allowed as per the rules I mentioned above because one of the pointers is a mutable pointer.

对此你能做些什么?

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

One option is to remove an entity from the entities vector before calling update on it, then inserting it back after the call. This solves the aliasing problem because we can't get another alias to the entity from the game state. However, removing the entity from the vector and reinserting it are operations with linear complexity (the vector needs to shift all the following items), and if you do it for each entity, then the main update loop runs in quadratic complexity. You can work around that by using a different data structure; this can be as simple as a Vec<Option<Entity>>, where you simply take the Entity from each Option, though you might want to wrap this into a type that hides all None values to external code. A nice consequence is that when an entity has to interact with other entities, it will automatically skip itself when iterating on the entities vector, since it's no longer there!

上述的一种变体是简单地获得整个实体向量的所有权,并用空的实体向量临时替换游戏状态的实体向量.

A variation on the above is to simply take ownership of the whole vector of entities and temporarily replace the game state's vector of entities with an empty one.

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 将无法与其他实体交互.

This has one major downside: Entity::update will not be able to interact with the other entities.

另一种选择是将每个实体包装在 RefCell.

Another option is to wrap each entity in a 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 中的值的可变指针.

By using RefCell, we can avoid retaining a mutable borrow on self. Here, we can use iter instead of iter_mut to iterate on entities. In return, we now need to call borrow_mut to obtain a mutable pointer to the value wrapped in the RefCell.

RefCell 本质上是在运行时执行借用检查.这意味着您最终可能会编写编译良好但在运行时出现恐慌的代码.例如,如果我们这样写 Entity::update:

RefCell essentially performs borrow checking at runtime. This means that you can end up writing code that compiles fine but panics at runtime. For example, if we write Entity::update like this:

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,它仍然被 GameState:: 中的 borrow_mut 调用借用了.更新.Entity::update 没有足够的信息来知道哪个实体是 self,因此您必须使用 try_borrowborrow_state(从 Rust 1.12 开始都是不稳定的.1) 或将额外数据传递给 Entity::update 以避免这种方法出现恐慌.

That's because we end up calling borrow on the entity that we're currently updating, which is still borrowed by the borrow_mut call done in GameState::update. Entity::update doesn't have enough information to know which entity is self, so you would have to use try_borrow or borrow_state (which are both unstable as of Rust 1.12.1) or pass additional data to Entity::update to avoid panics with this approach.

这篇关于在迭代器循环中对容器对象的可变引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆