10

以下は、2 つのボールが跳ね回る長方形の領域であるフィールドを使用した単純なシミュレーションです。Field構造体には、各ボールupdateを呼び出すメソッドがあります。update彼らの方法では、ボールはupdate速度に基づいて動き回る必要があります。しかし、フィールドの境界だけでなく、互いに反応する必要もあります。

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

Ball境界と他のボールに関する情報を構造体の更新関数に取得するにはどうすればよいですか? の次の行Field::updateはコンパイルされません。

self.balls[0].update(self);
self.balls[1].update(self);

次のエラーが発生します。

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

私は理解していますが、これを回避する方法がわかりません。

4

2 に答える 2

9

現在、構造体は、自身を更新できるようにするためにBall、それが含まれている について知る必要があります。Fieldこれはコンパイルされません。これは、循環参照とミューテーションが組み合わされた結果になるためです。Cellorを使用してこれを機能させることもできますがRefCell(後者はパフォーマンス コストがかかります)、コードを別の方法で構造化した方がよいでしょう。構造体が衝突をFieldチェックして解決できるようにします。構造体の関数は、の位置の更新を処理できます。BallBallBallWallBallupdateBall

// 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
}
于 2015-06-06T10:43:44.587 に答える
5

より小さな例を次に示します。

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

問題

への参照を渡すと、が変更できないことがField保証されます ( 「不変参照」の不変部分)。ただし、このコードはその一部であるボールも変更しようとしています。の実装において、どの参照が信頼できるもの、またはであるべきか?FieldselffieldBall::update

解決策: 必要なフィールドのみを使用してください

構造体の必要な部分と不要な部分を分離して、関数を呼び出す前にupdateそれらを使用できます。update

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

これらの断片的な参照をきちんとしたパッケージにまとめることもできます:

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

または、含まれている構造体を再構築して、最初から情報を分離します。

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

解決策: からメンバーを削除しますself

Ballに、変更を加える前にからを削除することもできますField。を簡単/安価に作成できる場合はBall、置き換えてみてください。

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

新しい値を簡単に作成できない場合は、Optionand takeitを使用できます。

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

解決策: ランタイム チェック

次の方法で、借用チェックをコンパイル時ではなく実行時に移動できます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)
    }
}
于 2016-08-14T17:03:38.573 に答える