Reputation: 1787
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).
Upvotes: 25
Views: 4162
Reputation: 14021
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);
}
Upvotes: 19
Reputation: 22163
In order to allow mutability in an Iterator
, you should use iter_mut()
and work on mutable references (&mut message
). Then, to avoid the additional borrow, you could just perform the addition in the body of the loop:
for &mut message in self.server.iter_mut() {
println!("Received {}", message);
*self.counters.entry(message).or_insert(0) += 1;
}
Upvotes: 3
Reputation: 71899
Given that you don't need the full ServerReader
for processing a message, you could make process_message
a free function and just pass &mut self.counters
to it. Then you have disjoint borrows of server
and counters
, which is fine.
Or if your non-server
part of ServerReader
is larger, extract that into its own struct, and make process_message
an impl method of that struct.
Upvotes: 5