Reputation: 1387
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);
}
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
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