无法在安全的Rust中创建循环链表; 不安全版本崩溃

asd*_*fle 2 circular-list rust

我正在写一个小型的战略游戏,但我在实现循环链​​表方面存在问题.

游戏涉及几个人一个接一个地采取行动,直到比赛结束.我可以通过使用循环链表来完成,其中每个元素都是一个引用下一个玩家的玩家.结构是这样的:

#[derive(Debug, Clone)]
struct Player {
    name: String,
    killed: bool,
    next: Option<Box<Player>>,
}
Run Code Online (Sandbox Code Playgroud)

我还想要一个指向当前活动播放器的指针并能够修改它的状态,但我认为Rust不允许我对同一个对象有两个可变引用,因为每个播放器已经有一个对下一个播放器的可变引用.

我想出的是,我可以使用一个简单的可变引用,该引用Box由其之前的播放器拥有并指向当前播放器.我写了一个简单的main函数,出现问题:

fn main() {
    let mut p3: Player = Player {
        name: "C".to_string(),
        killed: false,
        next: None,
    };
    let mut p2: Player = Player {
        name: "B".to_string(),
        killed: false,
        next: Some(unsafe { Box::from_raw(&mut p3) }),
    };
    let mut p1: Player = Player {
        name: "A".to_string(),
        killed: false,
        next: Some(unsafe { Box::from_raw(&mut p2) }),
    };
    p3.next = Some(unsafe { Box::from_raw(&mut p1) });
    println!("GAME STARTED!");
    let mut current_player = p3.next.as_mut().unwrap();
    let mut count = 0;
    while count < 10 {
        println!("Player to kill/save {}", current_player.name);
        (*current_player).killed = !(*current_player).killed;
        println!(
            "Player {} is killed: {}",
            (*current_player).name,
            (*current_player).killed
        );
        current_player = (*current_player).next.as_mut().unwrap();
        count = count + 1
    }
    println!("End!");
}
Run Code Online (Sandbox Code Playgroud)

错误也与可变性有关,但我不知道如何解决它.我想知道是否有更好的方法在Rust中实现这个想法,而不是使用循环链表和指向当前播放器的指针.也许我应该切换到另一个结构?

错误消息很长,这里是前几行:

error[E0502]: cannot borrow `current_player` (via `current_player.name`) as immutable because `current_player` is also borrowed as mutable (via `current_player.next`)
  --> src/main.rs:29:44
   |
29 |         println!("Player to kill/save {}", current_player.name);
   |                                            ^^^^^^^^^^^^^^^^^^^ immutable borrow of `current_player.name` -- which overlaps with `current_player.next` -- occurs here
...
36 |         current_player = (*current_player).next.as_mut().unwrap();
   |                          ---------------------- mutable borrow occurs here (via `current_player.next`)
...
40 | }
   | - mutable borrow ends here
Run Code Online (Sandbox Code Playgroud)

如果我改变了as_mut()对方法as_ref()返回的不可改变的参考Box和注释行

// (*current_player).killed = !(*current_player).killed;
Run Code Online (Sandbox Code Playgroud)

程序可以成功构建,但完成后会出现未知的运行时错误.不知道为什么会发生这种情况.

GAME STARTED!
Player to kill/save A
Player A is killed: false
Player to kill/save B
Player B is killed: false
......
Player to kill/save C
Player C is killed: false
Player to kill/save A
Player A is killed: false
End!
error: An unknown error occurred
Run Code Online (Sandbox Code Playgroud)

She*_*ter 7

首先,你应该阅读学习Rust与完全太多链接列表.单链表不简单,不像有多少编程语言对待它们.圆形链表(或双链表)在涉及所有权时非常复杂,这是一个核心的Rust概念.

如果您有循环链表,谁拥有每个项目?这一点很重要,因为值的所有者预计会降低该值.

类似地,由于某种原因,不允许多个可变引用.如果你想要它们,有类似的类型RefCell允许你具有不直接对应于代码结构的可变性.

崩溃的原因就在这里:unsafe.你已经告诉编译器"这很酷,我知道我在做什么",然后你继续打破你所期望的所有保证.如果你想使用unsafe,你应该阅读The Rustonomicon:高级和不安全的Rust编程的黑暗艺术.

但这些都不是真正需要的; 只需使用Box::from_raw:

#[derive(Debug, Clone)]
struct Player {
    name: String,
    killed: bool,
}

fn main() {
    let mut players = vec![
        Player {
            name: "C".to_string(),
            killed: false,
        },
        Player {
            name: "B".to_string(),
            killed: false,
        },
        Player {
            name: "A".to_string(),
            killed: false,
        },
    ];

    println!("GAME STARTED!");

    let mut current_player_idx = 0;
    let player_count = players.len();

    for _ in 0..10 {
        let current_player = &mut players[current_player_idx];

        println!("Player to kill/save {}", current_player.name);
        current_player.killed = !current_player.killed;
        println!(
            "Player {} is killed: {}",
            current_player.name, current_player.killed
        );

        current_player_idx += 1;
        current_player_idx %= player_count;
    }
    println!("End!");
}
Run Code Online (Sandbox Code Playgroud)

请注意,您不需要任何显式解除引用.

  • 我可能会想在玩家的“Vec”上使用 [`Iterator::cycle`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.cycle) 而不是手动递增和包装索引。 (2认同)

krd*_*dln 3

在 Rust 中,&mut不仅意味着可变,而且意味着唯一(与Box<T>\xe2\x80\x93相同T,假定由 唯一拥有Box)。尝试解决它unsafe会违反不变量并导致未定义的行为。你得到的错误就是因为这个(我的猜测是你导致了双重释放(递归?))。如果你想留下来unsafe(不推荐),请坚持*mut在任何地方都使用指针。

\n\n

内部可变性是您想要做的。您应该熟悉单元模块。这篇关于内部可变性的博文也值得一读。

\n\n

所以,我会像这样重新定义你的结构:

\n\n
use std::cell::{Cell,RefCell};\nuse std::rc::Weak;\n\n#[derive(Debug, Clone)]\nstruct Player {\n    name: String,\n    killed: Cell<bool>,\n    next: RefCell<Option<Weak<Player>>>,\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

并让所有玩家落后Rc(引用计数指针)。如果您计划让所有玩家仅生活在主函数的堆栈上,

\n\n
    next: Cell<Option<&Player>>,\n
Run Code Online (Sandbox Code Playgroud)\n\n

应该足够了。

\n\n

另一种选择是将整个播放器放入Rc<RefCell<Player>>,但仅将可变部分放入单元格中被认为是很好的做法。

\n