2

I'm currently learning rust by writing a project which is a clone of the board game "Puerto Rico".

the Game struct: I chose the players member to be an array slice because it can only contain 3, 4 or 5 players (to my understanding, this is the perfect use case for an array slice inside a struct, instead of a Vec)

the Game::new function: Returns a new Game instance. It takes a list of the names of the players and an empty Vec and populates it with the appropriate Player instances then stores it in the Game as an array slice.

The problem is that this method of instantiating the Game struct seems kind of cumbersome and I feel like there is a way around it (as in passing only the names parameter and somehow creating the array slice inside the new function).

So, is there a way to do it? Should I just change the players member to a Vec?

I tried to move the vec!() declaration inside the new function but, of course, it doesn't work becuase it is dropped at the end of the block.

use super::board::Board;
use super::player::Player;
use super::planatation::ResourceType;

#[derive(Debug)]
pub struct Game<'a> {

    board: Board,
    players: &'a [Player],
    governor: &'a Player
}

impl<'a> Game<'a> {

    pub fn new(names: &[String], players: &'a mut Vec<Player>) -> Self {
        let num_of_players = names.len() as i32;
        let board = Board::new(num_of_players);

        if num_of_players < 3 || num_of_players > 5 {
            panic!("Only 3, 4 or 5 players may play");
        }

        if num_of_players < 5 {
            for (i, name) in names.iter().enumerate() {
                if i < 2 {
                    players.push(Player::new(name.to_string(), ResourceType::Indigo));
                } else {
                    players.push(Player::new(name.to_string(), ResourceType::Corn));
                }
            }
        } else { // num_of_player == 5
            for (i, name) in names.iter().enumerate() {
                if i < 3 {
                    players.push(Player::new(name.to_string(), ResourceType::Indigo));
                } else {
                    players.push(Player::new(name.to_string(), ResourceType::Corn));
                }
            }
        }

        Game {
            board: board,
            players: players,
            governor: &players[0]
        }
    }
}
Jim Morrison
  • 401
  • 4
  • 9

1 Answers1

2

As you already noticed, a slice does not own its data, it only references them. This is why you must create the players outside of the struct and pass them to the game struct. If you want your struct to hold the players, they must be a Vec instead of a slice.

If governor was not a member of the struct, I would suggest simply using a Vec (or ArrayVec) and be done with it. This, however would mean that governor cannot (generally) be a reference to this very same vector (see Why can't I store a value and a reference to that value in the same struct?).

Depending on the exact semantics and use cases of governor and the other, "regular" players, I would do one of the following:

  • Having a Vec (or ArrayVec) for "regular" players, and an extra field for the governor. This may be cumbersome if you often have to do the same for both "regular" players and the governor, but you could introduce a method that returns an iterator over all the players and the governor.
  • Having a Vec (or ArrayVec) for all players, and just store the index into the vector for the governor. As a variation, you could enforce that the governor is always the first element of the Vec holding all players.
  • Doing as you already did. (This, however, will possibly mean that you cannot easily mutate them (because you can only have one mutable reference at a time). You may work around this via interior mutability, but I am not sure if it is worth it)
  • Only store a Vec<String> holding the player names. From that, you could derive the number of players. If the kind of player is reasonably simple, you may get away with not even storing the players explicitly, but derive the kind of player by its index (which I suppose may be possible because of the way you determine Indigo or Corn). The disadvantage is that you do not have a player around, but maybe you can model the entire game without them.
phimuemue
  • 34,669
  • 9
  • 84
  • 115