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

  • 本文关键字:词汇 范围 循环 不可变 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);
}

操场

这给出了:

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 来写这个?

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

该错误表示不可变借用发生在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本身也必须"隐式"借用不变。

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

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.

这就解释了原因。至于如何解决它:从理论上讲,最简单的解决方案是在Gamepad上实现Clone(如果Gamepad的所有字段都实现clone,这可以很容易地用#[derive(Clone)]完成;标准实现基本上是通过在原始字段的所有字段上调用.clone来创建一个新对象),然后使用gp.clone()而不仅仅是gp

这对程序的内存使用有(可能可以忽略不计)的影响,但此外,如果Gamepad使用不实现Clone的外部库类型,则可能不可行 - 您无法在这些外部类型上实现Clone,因为您没有在项目中定义CloneSomeExternalType

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

如果你不需要在调用update_game后保持vec并使用它执行操作,请考虑以下解决方案:

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);
}

希望这有帮助。

最新更新