1

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Paul Praet
  • 1,367
  • 14
  • 25
  • In addition to what Shepmaster said, consider giving that mutable vector some thought: What would be the lifetime of the `&Gamepad` values in it? Also, does it make sense to have a `Rc<&Gamepad>`? The sections of the Rust book on ownership, borrowing and lifetimes might help you if you cannot answer these sub-questions. – E_net4 Mar 11 '18 at 19:46
  • 1
    This question is basically a duplicate of [Cannot borrow as mutable because it is also borrowed as immutable](https://stackoverflow.com/q/47618823/155423). If you agree, we can mark it as a duplicate. If you disagree, please further [edit] your question to show how they are different or to clarify your question towards which aspects you don't understand. – Shepmaster Mar 11 '18 at 21:23
  • Maybe I'm not understanding the intent here, but couldn't you make `Gamepad` derive `Clone`, change `vec` to `Vec`, and `vec.push(gp.clone())`? – NovaDenizen Mar 12 '18 at 19:11
  • @NovaDenizen : you can assume Gamepad has certain fields which cannot be cloned. – Paul Praet Mar 12 '18 at 20:33
  • @Shepmaster: I don't agree this is a duplicate. I am still struggling to see why the immutable borrow lasts so long that it prevents me to call update_game (which is outside of the lexical scope of the borrow above). I also tried updating the code with a Rc but that did not help either. – Paul Praet Mar 13 '18 at 20:13
  • This was [cross-posted to Reddit](https://www.reddit.com/r/rust/comments/84h7y2/help_immutable_borrow_lasts_longer_than_expected/?ref=share&ref_source=link). – Shepmaster Mar 15 '18 at 02:10

1 Answers1

2

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
MutantOctopus
  • 3,431
  • 4
  • 22
  • 31
  • Thanks for your detailed explanation ! – Paul Praet Mar 17 '18 at 13:06
  • @PaulPraet worth noting: Collecting into a `Vec`, and then doing something with each item in the `Vec`, is probably unnecessary to begin with. Unless you need to be able to reference all the Gamepads at once, you may be better of simply doing what you need to do in the body of the `for` loop itself. But, I don't know the specifics of your code, so do what you have to. – MutantOctopus Mar 18 '18 at 07:03