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).
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); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With