为什么循环中的不可变借用超出其词法范围?

时间:2018-03-11 18:53:42

标签: rust

我被困在借阅检查器上。

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

这给出了:

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

虽然我可以在某种程度上理解错误,但是来自C和Java背景,我真的很难摆脱这个问题。我很困惑为什么在for循环结束后不会释放不可变借用。你会怎么用惯用的Rust写这个?

1 个答案:

答案 0 :(得分:1)

错误措辞有点差,但我看到了你的问题。

错误说不可变借用发生在for循环中,这不太正确。相反,它出现在您标记的行上:vec.push(gp)

gp pong.players 中包含的对象的不可变引用。退出循环时,没有pong.players 本身的不可变引用,但 是一个向量,其中包含对该向量内对象的引用。

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

由于您对pong.players内的对象有明确的不可变引用,因此Rust必须将pong.players视为“隐式地”不可变地借用,以确保在仍有不可变引用的情况下不会突变其内容到那个项目。由于pong.playerspong的一个组成部分并且是“隐式”借用的,因此pong本身也必须“隐式”借用。

换句话说,ponggame_loop的借用持续如下:

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.

这解释了为什么。至于如何解决它:从理论上讲,最简单的解决方案是在Clone上实现Gamepad(可以使用{轻松完成{1}}如果所有#[derive(Clone)]的字段都实现Gamepad;标准实现基本上是通过在原始字段的所有字段上调用clone来创建新对象,然后使用.clone而非gp.clone()

这对程序的内存使用有一个(可能是微不足道的)影响,但是,如果gp使用未实现Gamepad的外部库类型,那么它是不可行的 - 你可以'在这些外部类型上实施Clone,因为您没有在项目中定义CloneClone

如果您无法使用SomeExternalType,则可能需要重构代码;重新考虑为什么你需要某些可变或不可变的借用,如果没有必要则删除它们。如果失败了,您可能需要查看其他类型的指针,例如impl Clone,我没资格提供相关信息!

如果您在调用Cell后不需要保留vec并执行操作,请考虑以下解决方案:

update_game

希望这有帮助。