Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Manipulating an object from inside a loop that borrows it

Tags:

I'm writing some code in Rust that connects to a remote server, and depending on the messages sent by that server, computes some statistics or executes actions based on these statistics. But this is more of a learning project for me and I've run into an issue.

Here is the code that I have reduced to a bare minimum to reproduce the problem :

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

While I think I understand why the compiler is unhappy, I'm struggling to come up with a solution. I cannot manipulate my structure outside of the loop, since I have to work while connected and listening to the server. I also could put everything directly in the loop and not call any method, but I don't want to end up with a 1000 line loop (and I'd prefer to understand what an actual solution would look like).

like image 688
Shtong Avatar asked Oct 24 '16 09:10

Shtong


1 Answers

As you've worked out, you can't call a &mut self method while you're borrowing part of self, so you need to restructure somehow.

The way I would do it is to split the state needed by process_message into a separate type (in your example that's basically the HashMap, but in the real application it's likely to contain more), and move the method to that type. This works because you can separately borrow fields from a struct.

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

An alternative (which may or may not be possible in your real example) would be to avoid borrowing in the loop, making it more like:

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); } 
like image 81
Chris Emerson Avatar answered Sep 21 '22 20:09

Chris Emerson