Unable to create a circular linked list in safe Rust; unsafe version crashes

狂风中的少年 提交于 2019-12-13 16:25:12

问题


I am writing a small strategic game, but I have a problem with implementing a circular linked list.

The game involves several people taking actions one by one and round by round until the game ends. I though that this could be done by using a circular linked list where each element is a player with a reference to the next player. The structure is like this:

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

I also want a pointer to the current active player and be able to modify the status of it, but I think Rust does not allow me to have two mutable references to the same object because each player already has a mutable reference to the next player.

What I came up with is that I can use a simple mutable reference to the Box which is owned by its previous player and pointing to the current player. I wrote a simple main function where the problem occurs:

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

The error is also about the mutability but I have no idea how to fix it. I wonder if there is a better way to implement the idea in Rust rather than using a circular linked list and a pointer to the current player. Maybe should I switch to another structure?

The error message is quite long, here are the first few lines:

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

If I change the as_mut() method to as_ref() which returns the immutable reference of the Box, and comment the line

// (*current_player).killed = !(*current_player).killed;

The program can build successfully but there will be an unknown runtime error when it finishes. Don't know why that happens either.

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

回答1:


First, you should go read Learning Rust With Entirely Too Many Linked Lists. Singly-linked lists are not simple, unlike how many programming languages treat them. Circular linked lists (or doubly-linked lists) are quite complicated when it comes to ownership, a core Rust concept.

If you have a circular linked list, who owns each item? This is important to know because the owner of a value is expected to drop the value.

Similarly, multiple mutable references are disallowed for a reason. If you want them, there are types like RefCell that allow you to have mutability that doesn't directly correspond to the structure of the code.

The reason of the crash is right here: unsafe. You've told the compiler "this is cool, I know what I'm doing" and then you proceed to break all the guarantees that you are expected to uphold. If you want to use unsafe, you should read The Rustonomicon: The Dark Arts of Advanced and Unsafe Rust Programming.

In this case, Box::from_raw specifically warns against what you are doing:

After calling this function, the raw pointer is owned by the resulting Box. Specifically, the Box destructor will call the destructor of T and free the allocated memory. Since the way Box allocates and releases memory is unspecified, the only valid pointer to pass to this function is the one taken from another Box via the Box::into_raw function.


You don't need to create a linked list though; just use a Vec:

#[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!");
}

Note that you don't need any explicit dereferencing.




回答2:


In Rust &mut means not only mutable, but unique (same with Box<T>T is assumed to be uniquely owned by Box). Trying to work it around with unsafe will violate invariants and will lead to undefined behaviour. The error you get is because of that (my guess would be you're causing double free (recursively?)). If you want to stay with unsafe (not recommended), stick to using *mut pointers everywhere.

Interior mutability is what you want to do. You should familiarize yourself with the cell module. This blogpost about interior mutability is also worth reading.

So, I'd redefine your struct like that:

use std::cell::{Cell,RefCell};
use std::rc::Weak;

#[derive(Debug, Clone)]
struct Player {
    name: String,
    killed: Cell<bool>,
    next: RefCell<Option<Weak<Player>>>,
}

and keep all the players behind Rc (reference counted pointer). If you plan for all your players to only live on the stack of the main function,

    next: Cell<Option<&Player>>,

should be enough.

Another option is to put the whole player in Rc<RefCell<Player>>, but it's considered good practice to put only mutable parts in cells.



来源:https://stackoverflow.com/questions/38353845/unable-to-create-a-circular-linked-list-in-safe-rust-unsafe-version-crashes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!