The following is a simple simulation with a field which is a rectangular area with two balls bouncing around in it. The Field
struct has an update
method, which calls update
on each of the balls. The balls, in their update
method, need to move around based on their velocity. But they also need to react to each other, as well as the boundaries of the field.:
fn main() {
let mut field = Field::new(Vector2d { x: 100, y: 100 });
field.update();
}
#[derive(Copy, Clone)]
struct Vector2d {
x: i32,
y: i32,
}
struct Ball {
radius: i32,
position: Vector2d,
velocity: Vector2d,
}
impl Ball {
fn new(radius: i32, position: Vector2d, velocity: Vector2d) -> Ball {
Ball {
radius: radius,
position: position,
velocity: velocity,
}
}
fn update(&mut self, field: &Field) {
// check collisions with walls
// and other objects
}
}
struct Field {
size: Vector2d,
balls: [Ball; 2],
}
impl Field {
fn new(size: Vector2d) -> Field {
let position_1 = Vector2d {
x: size.x / 3,
y: size.y / 3,
};
let velocity_1 = Vector2d { x: 1, y: 1 };
let position_2 = Vector2d {
x: size.x * 2 / 3,
y: size.y * 2 / 3,
};
let velocity_2 = Vector2d { x: -1, y: -1 };
let ball_1 = Ball::new(1, position_1, velocity_1);
let ball_2 = Ball::new(1, position_2, velocity_2);
Field {
size: size,
balls: [ball_1, ball_2],
}
}
fn update(&mut self) {
// this does not compile
self.balls[0].update(self);
self.balls[1].update(self);
}
}
How do I get the information about the boundaries and the other ball to the Ball
struct's update function? These lines in the Field::update
do not compile:
self.balls[0].update(self);
self.balls[1].update(self);
Giving the following error:
error[E0502]: cannot borrow `*self` as immutable because `self.balls[..]` is also borrowed as mutable
--> src/main.rs:62:30
|
62 | self.balls[0].update(self);
| ------------- ^^^^- mutable borrow ends here
| | |
| | immutable borrow occurs here
| mutable borrow occurs here
which I understand, but I don't know how to get around this.
Currently your
Ball
struct needs to know about theField
it's contained in to be able to update itself. This doesn't compile because the result would be cyclic references combined with mutation. You could make this work by usingCell
orRefCell
(the latter having a performance cost) but it would be even better to structure the code differently. Let theField
struct check for and resolveBall
-Ball
andBall
-Wall
collisions. TheBall
struct'supdate
function can handle updating theBall
's position.Here's a smaller example:
The problem
When you pass in a reference to
Field
, you are making the guarantee that theField
cannot change (the immutable part of "immutable reference"). However, this code is also attempting to mutate a part of it: the ball! Which reference should be authoritative,self
orfield
, in the implementation ofBall::update
?Solution: use only the fields you need
You can separate the parts of the structure needed for
update
and those not needed and use them before calling theupdate
function:You can even bundle these piecemeal references up into a tidy package:
Or restructure your containing struct to separate the information from the beginning:
Solution: remove the member from
self
You could also go the other way and remove the
Ball
from theField
before making any changes to it. If you can easily / cheaply make aBall
, try replacing it:If you can't easily make a new value, you can use an
Option
andtake
it:Solution: runtime checks
You can move borrow checking to runtime instead of compile-time via
RefCell
: