Trying to understand the borrow checker

It's often that I will run into a situation where I'll need to do something like this

use std::collections::HashMap;
struct Connection {}
impl Connection {
    pub fn recv(&mut self) -> Option<String> {
        // ...
        None
    }
}
struct Server {
    connections: HashMap<std::net::SocketAddr, Connection>,
}
impl Server {
    pub fn process_packet(&mut self, addr: &std::net::SocketAddr, packet: String) {
        // ...
    }
    pub fn packets_update(&mut self) {
        for (addr, c) in self.connections.iter_mut() {
            if let Some(packet) = c.recv() {
                self.process_packet(addr, packet);
            }
        }
    }
}

However

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src\main.rs:19:17
   |
17 |         for (addr, c) in self.connections.iter_mut() {
   |                          ---------------------------
   |                          |
   |                          first mutable borrow occurs here
   |                          first borrow later used here
18 |             if let Some(packet) = c.recv() {
19 |                 self.process_packet(addr, packet);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here

How to do something like this while avoiding such a problem? I always know there is a rust way and I'm wondering what is the rust way for this.
Also, Why is this a problem? I know data raises and all but isn't that something only thread specific? Isn't that kind of complicating things assuming you use something that you actually don't? I'm really curious and whenever I try to find an answer it always goes back to threading and mutating something at the same exact time, Which is obviously not what I'm trying to do most of the time, Heck, I come to truly hate threads thanks to that.

It's a common misconception that data races only occur in multi-threaded or concurrent code. Single-threaded race conditions are just as bad. Iterator invalidation is the most famous example that borrow checking (the shared-xor-mutable rule) can prevent; read more about it here and here. This has nothing to do with multi-threading, yet it could lead to undefined behavior if it were allowed.


The most specific problem in your code is that process_packet() mutably borrows the entirety of self by means of its signature. If you know you only need fields disjoint from self.connections() in that method (which is the only way to make your program correct), then you can just pass those fields explicitly and separately to process_packet(), instead of passing all of self.

1 Like

I see, Rust is the first low leveled language I use seriously enough so I had no idea such a thing could exist.
The solution will unfortunately not work because process_packet, Will need to change some states most of the time so it needs self to be mutable, Which leaves me the option of the only way to go forward is to rethink and rewrite the entire way things are done, Which is not something I look forward into, For reference, I'm creating a single threaded, No async game server using a reliable udp library, Process_packets could handle all packets which will end up modifying almost everything which means it should become mutable, Connections is where raw connections go, And there will be another field containing a player data structure that looks like the connections field I sent above, But it seems this way is not really a good option, Any recommendations? Ways to structure things, Things to avoid? As I said I hate threads and async, So I'm not going to use them.

My point is not whether self is borrowed mutably or not; the point is to not borrow all of self, only some fields, maybe mutably (if you need that).

Which specific fields does the process_packets() function need?

Another HashMap field, The player one

In this case, modify the signature of process_packets() so that it takes that field only instead of &mut self:

fn process_packet(
    players: &mut HashMap<K, V>, // substitute correct key/value types
    addr: &std::net::SocketAddr,
    packet: String,
) {
    // ...
}

fn packets_update(&mut self) {
    for (addr, c) in self.connections.iter_mut() {
        if let Some(packet) = c.recv() {
            Server::process_packet(&mut self.players, addr, packet);
        }
    }
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.