I'm writing a game engine. In the engine, I've got a game state which contains the list of entities in the game.
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
Can anyone give me some suggestions on better ways to design this that fits with Rust better?
Thanks!
First, let's answer the question you didn't ask: Why is this not allowed?
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.Now, let's look at the signature of
Entity::update
:This method takes two parameters: a
&mut Entity
and a&GameState
. But hold on, we can get another reference toself
through the&GameState
! For example, suppose thatself
is the first entity. If we do this:then
self
andself_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.What can you do about this?
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 aVec<Option<Entity>>
, where you simplytake
theEntity
from eachOption
, though you might want to wrap this into a type that hides allNone
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.
This has one major downside:
Entity::update
will not be able to interact with the other entities.Another option is to wrap each entity in a
RefCell
.By using
RefCell
, we can avoid retaining a mutable borrow onself
. Here, we can useiter
instead ofiter_mut
to iterate onentities
. In return, we now need to callborrow_mut
to obtain a mutable pointer to the value wrapped in theRefCell
.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 writeEntity::update
like this:the program will panic:
That's because we end up calling
borrow
on the entity that we're currently updating, which is still borrowed by theborrow_mut
call done inGameState::update
.Entity::update
doesn't have enough information to know which entity isself
, so you would have to usetry_borrow
orborrow_state
(which are both unstable as of Rust 1.12.1) or pass additional data toEntity::update
to avoid panics with this approach.