Suraj
Suraj

Reputation: 191

How to reference multiple members in an array?

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

Answers (2)

A.B.
A.B.

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

huon
huon

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

Related Questions