Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 392
asdetrefle Avatar asked Jul 13 '16 14:07

asdetrefle


People also ask

Which of the following scheduling algorithms uses circular Linkedlist as a data structure for?

Circular Linked Lists is the basic idea for the round robin scheduling algorithm.

What is true about circular linked list?

In a circular linked list, as the name suggests, the list does not end; instead, it loops around. The last element of a circular linked list points to the head instead of pointing to null . A circular linked list can be implemented as a singly linked list or a doubly linked list.

What is circular linked list with example?

What is Circular linked list? The circular linked list is a linked list where all nodes are connected to form a circle. In a circular linked list, the first node and the last node are connected to each other which forms a circle. There is no NULL at the end.

Does rust have linked list?

Linked List is one of the most known data structures implemented in every language. It is used to build data structures on top of it such as stack or queue etc.


2 Answers

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.

like image 146
Shepmaster Avatar answered Nov 05 '22 11:11

Shepmaster


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.

like image 35
krdln Avatar answered Nov 05 '22 10:11

krdln