Search code examples
rustlifetimeselfborrow-checkerborrowing

How can multiple parts of self be borrowed here? Isn't self borrowed mutably as well as immutably here?


I have this struct:

struct PhysicsState {
    nodes: Vec<Node>,
    edges: Vec<Edge>,
}

and I'm trying to understand why this code compiles:

impl PhysicsState {
    fn remove_edge(&mut self, edge_index: usize) {
        let edge = &self.edges[edge_index];    // first borrow here
        // update the edge-index collection of the nodes connected by this edge
        for i in 0..2 {
            let node_index = edge.node_indices[i];
            self.nodes[node_index].remove_edge(edge_index);   // second (mutable) borrow here ?
        }
    }
}

while this fails:

impl PhysicsState {
    pub fn edge_at(&self, edge_index: usize) -> &Edge {
        &self.edges[edge_index]
    }

    pub fn node_at_mut(&mut self, node_index: usize) -> &mut Node {
        &mut self.nodes[node_index]
    }

    fn remove_edge(&mut self, edge_index: usize) {
        let edge = self.edge_at(edge_index);    // first (immutable) borrow here
        for i in 0..2 {
                let node_index = edge.node_indices[i];
                self.node_at_mut(node_index).remove_edge(edge_index);   // second (mutable) borrow here -> ERROR
            }
        }
    }
}

I originally used the first version and later changed it to the second, only to see it fail. And it makes sense to me that it fails. self is clearly borrowed as immutable first and then as mutable, which fails, as expected.

What I don't understand is: How does the first version work?

Clearly the first borrow (getting the &Edge) has to stay alive throughout the for loop, since it is used there. But how does it manage to get an additional mutable reference to a Node from self then?

The error returned by the compiler for the second version is: error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable

Why don't I get this error when using the first version?

In case you wonder:

  • Node and Edge are simple structs, not implementing Copy, so that's not what's happening here
  • The reason why I wanted to switch to the second version is that the two additional functions there actually contain type casts to usize, which I removed here for the sake of readability, but which I'd have to repeat everywhere in my code without these functions.

Perhaps I could instead use a macro to achieve the same effect, but over all I just wanna know how the borrowing works here, because it seems to me that I have some kind of misunderstanding about it.

Thanks!


Solution

  • The reason you can borrow self.edges and subsequently self.nodes in your first version, is because the compiler understands that self.edges and self.nodes are individually what's being borrowed. This is also what's called "Splitting Borrows" in relation to structs.

    However, if you opaquely look at the method signature:

    fn edge_at(&self, edge_index: usize) -> &Edge
    

    Then by looking at that, do you know what's being borrowed? Not really. All you can see is that it returns &Edge and &self is being borrowed. Thereby self as a whole is what's being borrowed, which disallows you doing the subsequent mutable borrow of self.nodes, because self is already immutably borrowed.


    What you essentially desire occurring, is that calling methods allow &self to be partially borrowed. This is not supported in Rust. However, there is an RFC dating back to 2015 requesting this feature. The RFC is titled "Partial Borrowing (#1215)", which discusses potential syntax and semantics.