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
Circular Linked Lists is the basic idea for the round robin scheduling algorithm.
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? 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.
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.
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, theBox
destructor will call the destructor ofT
and free the allocated memory. Since the wayBox
allocates and releases memory is unspecified, the only valid pointer to pass to this function is the one taken from anotherBox
via theBox::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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With