Passing mutable self reference to method of owned

2019-01-05 06:02发布

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.

2条回答
甜甜的少女心
2楼-- · 2019-01-05 06:40

Currently your Ball struct needs to know about the Field 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 using Cell or RefCell (the latter having a performance cost) but it would be even better to structure the code differently. Let the Field struct check for and resolve Ball-Ball and Ball-Wall collisions. The Ball struct's update function can handle updating the Ball's position.

// Ball's update function
fn update(&mut self) {
    // update position
}

// Field's update function
fn update(&mut self) {
    for ball in self.balls.iter_mut() {
        ball.update();
    }

    // check for collisions

    // resolve any collisions
}
查看更多
霸刀☆藐视天下
3楼-- · 2019-01-05 06:49

Here's a smaller example:

struct Ball {
    size: u8,
}

impl Ball {
    fn update(&mut self, field: &Field) {}
}

struct Field {
    ball: Ball,
}

impl Field {
    fn update(&mut self) {
        self.ball.update(self)
    }
}

The problem

When you pass in a reference to Field, you are making the guarantee that the Field 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 or field, in the implementation of Ball::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 the update function:

struct Ball {
    size: u8,
}

impl Ball {
    fn update(&mut self, field: &u8) {}
}

struct Field {
    players: u8,
    ball: Ball,
}

impl Field {
    fn update(&mut self) {
        self.ball.update(&self.players)
    }
}

You can even bundle these piecemeal references up into a tidy package:

struct Ball {
    size: u8,
}

impl Ball {
    fn update(&mut self, field: BallUpdateInfo) {}
}

struct BallUpdateInfo<'a> {
    players: &'a u8,
}

struct Field {
    players: u8,
    ball: Ball,
}

impl Field {
    fn update(&mut self) {
        let info = BallUpdateInfo { players: &self.players };
        self.ball.update(info)
    }
}

Or restructure your containing struct to separate the information from the beginning:

struct Ball {
    size: u8,
}

impl Ball {
    fn update(&mut self, field: &UpdateInfo) {}
}

struct UpdateInfo {
    players: u8,
}

struct Field {
    update_info: UpdateInfo,
    ball: Ball,
}

impl Field {
    fn update(&mut self) {
        self.ball.update(&self.update_info)
    }
}

Solution: remove the member from self

You could also go the other way and remove the Ball from the Field before making any changes to it. If you can easily / cheaply make a Ball, try replacing it:

use std::mem;

#[derive(Default)]
struct Ball {
    size: u8,
}

impl Ball {
    fn update(&mut self, field: &Field) {}
}

struct Field {
    ball: Ball,
}

impl Field {
    fn update(&mut self) {
        let mut ball = mem::replace(&mut self.ball, Ball::default());
        ball.update(self);
        self.ball = ball;
    }
}

If you can't easily make a new value, you can use an Option and take it:

struct Ball {
    size: u8,
}

impl Ball {
    fn update(&mut self, field: &Field) {}
}

struct Field {
    ball: Option<Ball>,
}

impl Field {
    fn update(&mut self) {
        if let Some(mut ball) = self.ball.take() {
            ball.update(self);
            self.ball = Some(ball);
        }
    }
}

Solution: runtime checks

You can move borrow checking to runtime instead of compile-time via RefCell:

use std::cell::RefCell;

struct Ball {
    size: u8,
}

impl Ball {
    fn update(&mut self, field: &Field) {}
}

struct Field {
    ball: RefCell<Ball>,
}

impl Field {
    fn update(&mut self) {
        self.ball.borrow_mut().update(self)
    }
}
查看更多
登录 后发表回答