rust borrow-checker

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