Borrowing references to struct attributes

Hello,

I was looking for an additional explanation around rules on borrowing references to struct and its attributes. Apologies if this was asked before I’m unable to reason one aspect of these rules.

The Rust Book clearly states that

  • “At any given time, you can have either one mutable reference or any number of immutable references.”

The gap that I’m trying to fill is around methods which take a mutable reference to an instance of a struct and then borrow references to its attributes within the method’s body.

Take the following method for instance:

fn update(&mut self) {
    self.ball.update(&self.players)
}
  1. Since calling Ball.update requires mutable reference to struct’s ball attribute, wouldn’t it conflict with immutable borrow to struct’s players attribute?

  2. In the sample code below, Field.update method compiles fine. However, alternative implementation update_1a/update_1b fails to compile. Could someone explain why the second variant is not allowed by the compiler?

Thanks in advance,
-Sebastian

Sample Code:

struct Ball {
    size: u8,
}

impl Ball {
    fn update(&mut self, field: &u8) {}
}

struct Field {
    players: u8,
    ball: Ball,
}

impl Field {
    fn update(&mut self) {
        self.ball.update(&self.players)
    }
    
    fn update_1a(&mut self) {
        self.update_1b(&self.players)
    }
    
    fn update_1b(&mut self, players: &u8) {
        self.ball.update(players)
    }
}

The first and foremost thing to understand is that function signature declares an interface - it’s not just a hint. This is critical to understand. The interface of all of update, update_1a, and update_1b is that they mutably borrow the entirety of Field for the duration of the routine.

When you are borrowing a structure mutably, you are then free to borrow any of the sub-elements mutably. HOWEVER, those sub-borrow imply a transitive borrow on the parent structure - after all, maintaining that borrow may be essential to enforcing certain invariants between fields of the struct.

So, take a look at update_1b. Its signature is asking for it to mutably borrow the entirety of self. However, update_1a cannot call update_1b in the way it does since a mutable borrow must be exclusive, and you’re attempting to simultaneously borrow the players part of the struct and the entire struct exclusively.

Many have tried to argue against this - that a sufficiently smart compiler should be able to “see” that update_1a's usage of update_1b is legal since update_1b is only borrowing the ball field. However, it’s not this simple - if ball and players need to be kept in sync in some way, especially to enforce some safety-critical property the compiler can’t see, then it would be illegal for the compiler to allow this.

5 Likes

You can reborrow a struct fields as long as the borrows are disjoint. This is what happens in Field::update: the field ball is &mutably (re)borrowed, and the field players is & (re)borrowed. Since they are disjoint, all is fine.

But in update_1a you are trying to (re)borrow both the players field and the whole self struct (As @AndrewGaspar said, only the function signatures matter; not their implementation). These borrows intersect on players, which ends up being held by two borrows, with one of then being exclusive (&mut), which violates borrowing rules; hence the error.

1 Like

I think the piece of information I was missing was on disjoint borrows. Interestingly enough, I did not catch or see that in the Rust Book so for someone who comes from OO background, not being able to do update_1a->update_1b is a bit puzzling.

Thank you @AndrewGaspar and @Yandros for the thorough explanations. Set analogy is a great way to visualize and reason though this problem.

Cheers!

1 Like

Let’s cc @carols10cents, they can give you the best answer regarding that point :slight_smile:

See https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html#mutable-references maybe?

I think a section on disjoint struct borrows would be a nice addition to already great documentation. :wink: