rust - Pasar auto-referencia mutable al método del objeto propio
borrow-checker (2)
Actualmente, su estructura de
Ball
necesita saber sobre el
Field
en el que se encuentra para poder actualizarse.
Esto no se compila porque el resultado sería referencias cíclicas combinadas con mutación.
Puede hacer que esto funcione utilizando
Cell
o
RefCell
(este último tiene un costo de rendimiento) pero sería aún mejor estructurar el código de manera diferente.
Deje que la estructura de
Field
compruebe y resuelva colisiones de
Ball
-
Ball
y
Ball
-
Wall
.
La función de
update
Ball
struct puede manejar la actualización de la posición de
Ball
.
// 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
}
La siguiente es una simulación simple con un campo que es un área rectangular con dos bolas que rebotan en él.
La estructura de
Field
tiene un método de
update
, que llama a la
update
en cada una de las bolas.
Las bolas, en su método de
update
, necesitan moverse según su velocidad.
Pero también deben reaccionar entre sí, así como a los límites del campo .:
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);
}
}
¿Cómo obtengo la información sobre los límites y la otra bola para la función de actualización de la estructura
Ball
?
Estas líneas en el
Field::update
no compilan:
self.balls[0].update(self);
self.balls[1].update(self);
Dando el siguiente 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
lo cual entiendo, pero no sé cómo solucionar esto.
Aquí hay un ejemplo más pequeño:
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)
}
}
El problema
Cuando pasa una referencia al
Field
, está garantizando que el
Field
no puede cambiar (la parte
inmutable
de la "referencia inmutable").
Sin embargo, este código también intenta mutar una parte de él: ¡la pelota!
¿Qué referencia debe ser autoritaria,
self
o de
field
, en la implementación de
Ball::update
?
Solución: use solo los campos que necesita
Puede separar las partes de la estructura necesarias para la
update
y las que no son necesarias y usarlas
antes de
llamar a la función de
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)
}
}
Incluso puede agrupar estas referencias fragmentarias en un paquete ordenado:
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)
}
}
O reestructura tu estructura de contención para separar la información desde el principio:
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)
}
}
Solución: eliminar al miembro de
self
También puede ir hacia el
otro
lado y eliminar la
Ball
del
Field
antes de hacer cualquier cambio.
Si puede hacer una
Ball
manera fácil / económica, intente reemplazarla:
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;
}
}
Si no puede crear fácilmente un nuevo valor, puede usar una
Option
y
take
:
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);
}
}
}
Solución: verificaciones de tiempo de ejecución
Puede mover la verificación de préstamos al tiempo de ejecución en lugar del tiempo de compilación a través de
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)
}
}