Reputation: 191
I have an array of two players. I have a variable, current_num which is equal to which player in the array is the current player. I have a while loop which iterates through the main game logic where sometimes current_num is updated, sometimes it stays the same. I would like to assigned a current_player and next_player variable each iteration of the loop as like so:
while !self.board.check_win() {
let ref mut current_player = self.players[current_num];
let ref mut next_player = self.players[(current_num+1)%2];
/* Game Logic */
}
This doesn't work because I try to borrow something from self.players[..] twice. I honestly don't even need the next_player variable if I could somehow store the next player inside the first player object, but you can't create cyclic data structures in rust it seems. I fought so hard with the compiler to accomplish the following:
player1.next = &player2;
player2.next = &player1;
Unfortunately that doesn't seem to be possible.... If it is, I would rather do that so that I could do something along the lines of:
current_player.next().do_something();
instead of needing a next_player variable. I would also be able to do:
current_player = current_player.next();
for switching to the next player so I wouldn't even have to keep an index variable (current_num).
Now I do have a working mode where I always refer to the current player as:
self.players[current_num].do_something() //current_player
self.players[(current_num+1)%2).do_something() //next_player
This avoids the borrowing issues, but makes for VERY verbose code that's hard to read. C/C++ are really much easier with regards to getting this kind of design working. I feel like I'm constantly fighting the compiler to get what I want done...
Any help would be greatly appreciated!
Upvotes: 0
Views: 385
Reputation: 16640
Alternatively:
use std::cell::RefCell;
struct Player;
fn main() {
let players = Vec::from_fn(3, |_| RefCell::new(Player));
let mut player_1 = players.get(0).borrow_mut();
let mut player_2 = players.get(1).borrow_mut();
//Happily mutate both players from here on
}
Normally mutating an object which has been borrowed multiple times isn't allowed. You can't have multiple &mut references to the same object, but multiple & references are allowed because they don't allow mutation. Since Cell and RefCell have internal mutability, we can borrow them via & reference while still mutating their contents.
Upvotes: 1
Reputation: 102106
To solve your immediate problem you can use the mut_split_at
method, this uses unsafe
code internally to give you two disjoint slices into a vector, resolving all your borrowing issues. You might write a wrapper like:
fn double_index<'a, T>(x: &'a mut [T],
i: uint, j: uint) -> (&'a mut T, &'a mut T) {
assert!(i != j, "cannot double_index with equal indices");
if i < j {
let (low, hi) = x.mut_split_at(j);
(&mut low[i], &mut hi[0])
} else { // i > j
let (low, hi) = x.mut_split_at(i);
(&mut hi[0], &mut low[j])
}
}
then write
let (current_player, next_player) = double_index(self.players,
current_num,
(current_num + 1) % 2);
(Assuming self.players
is a [Player, .. 2]
or &mut [Player]
. If it is a Vec<Player>
you will need to call .as_mut_slice()
explicitly.)
you can't create cyclic data structures in rust it seems
You can, using Rc
and Weak
. For shared mutability, you'll need to use RefCell
or Cell
. E.g.
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Player {
// ...
next: Option<Weak<RefCell<Player>>>
}
impl Player {
fn next(&self) -> Rc<RefCell<Player>> {
// upgrade goes from Weak to Rc & fails if this is invalid
self.next.unwrap().upgrade()
}
}
let player1 = Rc::new(RefCell::new(Player { ..., next: None }));
let player2 = Rc::new(RefCell::new(Player { ..., next: None }));
// join them up; downgrade goes from Rc to Weak
player1.borrow_mut().next = Some(player2.downgrade());
player2.borrow_mut().next = Some(player1.downgrade());
This avoids the borrowing issues, but makes for VERY verbose code that's hard to read. C/C++ are really much easier with regards to getting this kind of design working. I feel like I'm constantly fighting the compiler to get what I want done...
Any sort of shared mutability is very easy to get wrong; and in languages without a GC, this can lead to dangling pointers, segfaults & security holes. Unfortunately sealing all the holes in the manner that Rust does leads to this type of thing being rather ugly at times.
Upvotes: 2