rust borrow-checker

rust - Manipular un objeto desde dentro de un bucle que lo toma prestado.



borrow-checker (3)

Estoy escribiendo un código en Rust que se conecta a un servidor remoto y, según los mensajes enviados por ese servidor, calcula algunas estadísticas o ejecuta acciones basadas en estas estadísticas. Pero esto es más un proyecto de aprendizaje para mí y me he encontrado con un problema.

Aquí está el código que he reducido a un mínimo para reproducir el problema:

// Repro code for error[E0502]: cannot borrow `*self` as mutable because `self.server` is also borrowed as immutable use std::collections::HashMap; struct ServerReader { server: Vec<u32>, // A vec for demo purposes, but please imagine this is a server object counters: HashMap<u32, usize>, } impl ServerReader { fn new() -> ServerReader { ServerReader { server: vec!(1, 2, 5, 2, 7, 9, 1, 1, 5, 6), // Filling my "server" with some messages counters: HashMap::new(), } } fn run(&mut self) { println!("Connecting..."); // ... here there should be some code to connect to the server ... for message in self.server.iter() { // We wait for the network messages sent by the server, and process them as they come // ----------- immutable borrow occurs here println!("Received {}", message); self.process_message(*message); // HOW // ^^^^ mutable borrow occurs here } // - immutable borrow ends here println!("Disconnected"); } fn process_message(&mut self, message: u32) { // Please imagine that this function contains complex stuff let counter = self.counters.entry(message).or_insert(0); *counter += 1; } } fn main() { let mut reader = ServerReader::new(); reader.run(); println!("Done"); }

Aunque creo que comprendo por qué el compilador no está contento, me cuesta encontrar una solución. No puedo manipular mi estructura fuera del bucle, ya que tengo que trabajar mientras estoy conectado y escuchando el servidor. También podría poner todo directamente en el bucle y no llamar a ningún método, pero no quiero terminar con un bucle de 1000 líneas (y prefiero entender cómo sería una solución real).


Como has entrenado, no puedes llamar a un método de &mut self cuando te estás prestando parte de ti self , así que necesitas reestructurar de alguna manera.

La forma en que lo haría es dividir el estado necesario por process_message en un tipo separado (en su ejemplo es básicamente el HashMap , pero en la aplicación real es probable que contenga más), y mover el método a ese tipo. Esto funciona porque puede tomar prestados campos por separado de una estructura .

struct SomeState { counters: HashMap<u32, usize>, } impl SomeState { pub fn new() -> SomeState { SomeState { counters: HashMap::new(), } } fn process_message(&mut self, message: u32) { let counter = self.counters.entry(message).or_insert(0); *counter += 1; } } struct ServerReader { server: Vec<u32>, state: SomeState, } impl ServerReader { fn new() -> ServerReader { ServerReader { server: vec!(1, 2, 5, 2, 7, 9, 1, 1, 5, 6), state: SomeState::new(), } } fn run(&mut self) { println!("Connecting..."); for message in self.server.iter() { println!("Received {}", message); self.state.process_message(*message); } println!("Disconnected"); } }

Una alternativa (que puede o no ser posible en tu ejemplo real) sería evitar los préstamos en el bucle, haciéndolo más como:

loop { // if next_message() returns an owned message, ie not still borrowing // self let message = self.next_message(); // now no borrow left self.process_message(message); }


Dado que no necesita el ServerReader completo para procesar un mensaje, puede hacer que process_message una función gratuita y simplemente pasarle &mut self.counters . Luego, tiene prestados distintos de server y counters , lo cual está bien.

O si la parte que no es server de ServerReader es más grande, extraiga eso en su propia estructura, y haga que process_message un método implícito de esa estructura.


Para permitir la mutabilidad en un Iterator , debe usar iter_mut() y trabajar en referencias mutables ( &mut message ). Luego, para evitar el préstamo adicional, simplemente puede realizar la adición en el cuerpo del bucle:

for &mut message in self.server.iter_mut() { println!("Received {}", message); *self.counters.entry(message).or_insert(0) += 1; }