0

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!

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

What would self.borrow() be?
Whoops, it would be nonsense. I was just in Rc<RefCell<...>> mode. It should be called like player1.borrow().next() (where player1 is Rc<RefCell<Player>>), i.e. borrow on the outside.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.