Paul Praet
Paul Praet

Reputation: 1387

Why does an immutable borrow in a loop last outside of its lexical scope?

I am stuck on the borrow checker.

pub struct Gamepad {
    str: String,
}

pub enum Player {
    Human(Gamepad),
    Computer,
}

pub struct PlayerData {
    pub player: Player, /* actually this should be private */
}

struct Pong {
    players: Vec<PlayerData>,
}

fn update_game(_pong: &mut Pong) {}

fn main() {
    println!("Hello, world!");
    let mut pong = Pong {
        players: vec![
            PlayerData {
                player: Player::Computer,
            },
            PlayerData {
                player: Player::Human(Gamepad {
                    str: "mydev".to_string(),
                }),
            },
        ],
    };

    game_loop(&mut pong);
}

fn game_loop(pong: &mut Pong) {
    let mut vec: Vec<&Gamepad> = Vec::new();
    {
        for playerdata in pong.players.iter() {
            match playerdata.player {
                Player::Human(ref gp) => {
                    if gp.str == "mydev" {
                        vec.push(gp); //omitting this line of code fixes borrow checker issues
                    }
                }
                _ => {}
            }
        }
    }
    update_game(pong);
}

playground

This gives:

error[E0502]: cannot borrow `*pong` as mutable because `pong.players` is also borrowed as immutable
  --> src/main.rs:52:17
   |
41 |         for playerdata in pong.players.iter() {
   |                           ------------ immutable borrow occurs here
...
52 |     update_game(pong);
   |                 ^^^^ mutable borrow occurs here
53 | }
   | - immutable borrow ends here

While I can understand the error to some extent, but coming from a C and Java background, I really struggle to get out of this problem. I am mainly confused why the immutable borrow is not released after the for loop ends. How would you write this in idiomatic Rust?

Upvotes: 1

Views: 662

Answers (1)

MutantOctopus
MutantOctopus

Reputation: 3581

The error is a bit poorly worded, but I see your problem.

The error says the immutable borrow occurs in the for loop, which isn't quite correct. Instead, it occurs on the line you marked: vec.push(gp).

gp is an immutable reference to an object contained within pong.players. When you exit the loop, there is no immutable reference to pong.players itself, but there is a vector full of references to objects inside that vector.

pong.players : [ a,  b,  c,  d,  e]
                 ^   ^   ^   ^   ^
vec          : [&a, &b, &c, &d, &e]

Since you have outstanding immutable references to objects within pong.players, Rust has to consider pong.players as "implicitly" immutably borrowed, to ensure that none of its contents are mutated while there is still an immutable reference to that item. Since pong.players is a component of pong and is "implicitly" borrowed, pong itself has to be "implicitly" borrowed immutably as well.

In other words, the borrow of pong in game_loop lasts as such:

fn game_loop(pong: &mut Pong) {
    let mut vec: Vec<&Gamepad> = Vec::new();    // <+ `vec`'s lifetime begins here
    {                                           //  |
        for playerdata in pong.players.iter() { // <+ `pong.players.iter()` temporarily immutably borrows
                                                //  | `players` from `pong` for the iterator. `playerdata`
                                                //  | is a borrowed portion of `pong.players`.
                                                //  | As long as any `playerdata` exists, `pong.players`
                                                //  | is immutably borrowed by extension.
            match playerdata.player {           // <+ `playerdata.player` is a portion of `playerdata`.
                Player::Human(ref gp) => {      // <+ `gp` is a borrow of an element of `playerdata`.
                    if gp.str == "mydev" {      //  |
                        vec.push(gp);           // <! At this point, `gp` is added to `vec`.
                                                //  | Since `gp` is inside `vec`, the reference to `gp`
                                                //  | is not dropped *until `vec` is dropped.
                    }                           //  |
                }                               //  <- `gp`'s *lexical* lifetime ends here, but it may still
                                                //  | be inside `vec`. Any `gp` added to `vec` is still
                                                //  | considered borrowed.
                _ => {}                         //  |
            }                                   // <- `playerdata.player` is not longer lexically borrowed.
                                                //  | However, since `gp`, a portion of `playerdata.player`,
                                                //  | may still be borrowed, the compiler flags
                                                //  | `playerdata.player` as still borrowed.
        }                                       // <- `playerdata`'s borrow scope ends here, but since
                                                //  | `playerdata.player` may still be borrowed (due to the
                                                //  | fact that `vec` may contain references to elements of
                                                //  | playerdata.player), `playerdata` is still considered borrowed
    }                                           // <- the iterator over `pong.players` is dropped here. But since
                                                //  | `playerdata` might still be referenced in `vec`, `pong.players`
                                                //  | is still considered borrowed... and since `pong.players` is
                                                //  | implicitly borrowed, `pong` is implicitly borrowed.
    update_game(pong);                          // <! When you reach this line, `pong` is implicitly borrowed because
                                                //  | there are references to something 'inside' it. Since you can't
                                                //  | have an immutable borrow and a mutable borrow at the same time
                                                //  | (to ensure you can't change something at the same time another
                                                //  | part of the program views it), `update_game(pong)` cannot accept
                                                //  | a mutable reference to `pong`.
}                                               // <- At this point, `vec` is dropped, releasing all references to the
                                                //  | contents of `pong`. `pong` is also dropped here, because it is the
                                                //  | end of the function.

That explains the why. As for the how to solve it: Theoretically, the easiest solution would be to implement Clone on Gamepad (which can be easily done with #[derive(Clone)] if all of Gamepad's fields implement clone; the standard implementation is basically creating a new object by calling .clone on all of the fields of the original) and then use gp.clone() rather than just gp.

This has a (probably negligible) impact on the memory use of your program, but moreover, it can be infeasible if Gamepad uses external-library types that do not implement Clone - you can't implement Clone on those external types, because you don't define Clone or SomeExternalType in your project.

If impl Clone isn't available to you, you may need to refactor your code; reconsider why you need certain mutable or immutable borrows, and remove them if they're unnecessary. If that fails, you might need to look into other types of pointers like Cell, which I'm not qualified to give information about!

If you don't need to keep vec around and do stuff with it after update_game is called, consider this solution:

fn game_loop(pong: &mut Pong) {
    {
        let mut vec: Vec<&Gamepad> = Vec::new(); // <+ Vec is created
        for playerdata in pong.players.iter() {  //  |
            match playerdata.player {            //  |
                Player::Human(ref gp) => {       //  |
                    if gp.str == "mydev" {       //  |
                        vec.push(gp);            //  |
                    }                            //  |
                }                                //  |
                _ => {}                          //  |
            }                                    //  |
        }                                        //  |
        for g_pad in vec {                       //  |
            // Do something with each gamepad    //  |
        }                                        //  |
    }                                            // <- `vec` is dropped
    // Since `vec` no longer exists, there are no more references
    // to the contents of `pong`, and `update_game` can be called.
    update_game(pong);
}

Hope this helps.

Upvotes: 2

Related Questions