rustborrow-checkermutability

Why can't I borrow as mutable more than once at a time in this example?


Minimal example of a game with a player which owns a position and walks as time passes. The following compiles:

use std::thread::sleep;
use std::time::Duration;

struct Player {
    position: usize,
}

impl Player {
    fn new() -> Self {
        Self { position: 0 }
    }
}

impl Player {
    fn get_position(&self) -> usize {
        self.position
    }
}

impl Player {
    fn walk(&mut self) {
        self.position += 1;
    }
}

fn main() {
    let mut player = Player::new();
    loop {
        player.walk();
        sleep(Duration::from_secs(1));
    }
}

If the player borrows the position rather than owning it, it does not compile:

use std::thread::sleep;
use std::time::Duration;

struct Player<'a> {
    position: &'a mut usize,
}

impl<'a> Player<'a> {
    fn new(position: &'a mut usize) -> Self {
        Self { position }
    }
}

impl<'a> Player<'a> {
    fn get_position(&'a self) -> usize {
        *self.position
    }
}

impl<'a> Player<'a> {
    fn walk(&'a mut self) {
        *self.position += 1;
    }
}

fn main() {
    let mut position = 0;
    let mut player = Player::new(&mut position);
    loop {
        player.walk();
        sleep(Duration::from_secs(1));
    }
}

The error reads:

error[E0499]: cannot borrow `player` as mutable more than once at a time
  --> src/main.rs:30:9
   |
30 |         player.walk();
   |         ^^^^^^
   |         |
   |         `player` was mutably borrowed here in the previous iteration of the loop
   |         first borrow used here, in later iteration of loop

I can't understand why the second program does not compile when the first does. I understand that the compiler does not accept multiple mutable borrows, but isn't that also the case for the first one? The program runs the walk function which asks for a mutable reference to self, in the same way that the second program does.

In a case like this where the program needs to borrow data in a struct, then modify it, what is considered a good solution?


Solution

  • It's a common error

    impl<'a> Player<'a> {
        fn walk(&'a mut self) {
    

    The name of lifetimes have meaning. Since you're using the same lifetime in the structure and method, you're telling the compiler that calling walk borrows Player<'a> for 'a, which is the lifetime of the thing Player borrows. So you're telling the compiler that calling this method borrows (mutably, so no sharing) the structure for longer than the structure can exist, essentially locking yourself out of the structure entirely.

    The solution is trivial, just remove the one in the method:

    impl<'a> Player<'a> {
        fn walk(&mut self) {
    

    Now Rust can create a second lifetime (which you could name explicitly, as long as it's not 'a), and it's inferring that that lifetime is only as long as the function's body since nothing escapes.

    In fact since you don't need to make active use of 'a, you can also leave that one unnamed, you still have to pass it in since Player is generic over lifetimes and you can't just leave out the type parameters but the compiler is perfectly happy with

    impl Player<'_> {
        fn walk(&mut self) {