Proper way to initialize variables inside scope in Rust

Viewed 319

I'm new to Rust, and wanna write some simple game.

In the code below I have some peace of code, where I define riddler and guesser between 2 players of the game.

enum PlayerMovingPosition{
    Riddler,
    Guesser,
}

struct Player {
    name: String,
    moving_position: Option<PlayerMovingPosition>,
}

struct Game {
    player1: Player,
    player2: Player,
}

fn define_riddler_n_guesser(&self) {
    let mut riddler: &Player;
    let mut guesser: &Player;
    for player in [&self.player1, &self.player2] {
        match player.moving_position {
            Some(PlayerMovingPosition::Riddler) => riddler = player,
            Some(PlayerMovingPosition::Guesser) => guesser = player,
            _ => panic!("Hey damba, player {} moving position is undefined!", player.name.as_str()),
        }
    
    //... Place where I got the error
    }

I've got the E0381 error, that says that the variables may be unitialized there.

I went through some searching and got condractiory answers, like:

  • some people claimed that it's bad practice to declare variables without initializing it, and it leads to bugs; they adviced to use some iterationg magic I can't get now cause of my poor rust skill.
  • others said that it's a proper way, and is used in situations like:
let lol;
{
    lol = "kek";
}
println!(lol);
// kek

I found it contoversial and I can't decide what to do in my case, but I bet they aren't contradictiory, they are about different things, and I can't see this difference due to poor understanding.

It would be fine if you tell me if I wrote bad code in rust and show me the right one

3 Answers

You can solve this problem by using a the match pattern. This match pattern will assure both riddler and guesser are always assigned

        let (riddler, guesser) = match (&self.player1.moving_position, &self.player2.moving_position) {
            (Some(PlayerMovingPosition::Riddler), Some(PlayerMovingPosition::Guesser)) => (&mut self.player1, &mut self.player2),
            (Some(PlayerMovingPosition::Guesser), Some(PlayerMovingPosition::Riddler)) => (&mut self.player2, &mut self.player1),
            _ => panic!("Unsupported combo"),
        };

playground

Here are some of my observations:

  • It looks define_riddler_n_guesser is a method of the Game struct. If so, it needs to be put in impl Game { ... }. In that context, self represents the struct instance on which the method is called upon.
  • If the field moving_position of a player cannot be None, there is no need of using Option.
  • Both of the opinions on variable initiation are correct to me. A variable cannot be used without being initialized.

Here is the code revised based on the above points. Just for your reference.

#[derive(PartialEq, Eq)]
enum PlayerMovingPosition{
    Riddler,
    Guesser,
}

struct Player {
    name: String,
    moving_position: PlayerMovingPosition,
}

struct Game {
    player1: Player,
    player2: Player,
}

impl Game {
    fn define_riddler_n_guesser(&mut self) {
        let (riddler, guesser) = 
            if self.player1.moving_position == PlayerMovingPosition::Riddler { 
                (&mut self.player1, &mut self.player2)
            } else {
                (&mut self.player2, &mut self.player1)
            };
    }
}

Playground

Probably you could take a slightly different approach: Save the roles of the players in the game instance. That would make swapping the roles easier and fix your error (because the riddler and guesser is always be defined).

The code would be the following:

struct Player {
    name: String,
}

struct Game {
    player1: Player,
    player2: Player,
    player1_is_riddler: bool,
}

impl Game {
    fn define_riddler_n_guesser(&self) {
        let (riddler, guesser) = if self.player1_is_riddler {
            (&self.player1, &self.player2)
        } else {
            (&self.player2, &self.player1)
        };
        //... Place where you got the error
    }
}
Related