Assigning boolean values using if-else statements

I am trying to do something like this:

use std::io;

fn main() {
    let is_male : bool;
    let mut answer = String::new();

    println!("Are you a male? (Input Y or N)");
    io::stdin()
        .read_line(&mut answer)
        .expect("Failed to read line");

    if answer == "Y" || answer == "y" {
        is_male = true;
    }
    else if answer == "N" || answer == "n" {
        is_male = false;
    }
    else {
        println!("Invalid input!");
    }

    if is_male {
        println!("You are a male.");
    }
    else {
        println!("You are not a male.");
    }
}

The error I got is:
error[E0381]: use of possibly-uninitialized variable: is_male
--> src\main.rs:22:9
|
22 | if !is_male {
| ^^^^^^^ use of possibly-uninitialized is_male

Does Rust not allow assigning boolean values this way? Or is there something wrong with the code?

What should your code do in case of invalid input? It doesn't halt, so it will fall through to the if is_male.

2 Likes

OK. So why does that error occurs when my code do in case of invalid input?

Well, in this case you're not assigning anything to is_male, so it stays uninitialized when checked. This would be undefined behavior, if compiler allowed it to be used this way, since the value can be literally anything - any garbage which was stored in this memory before.

1 Like

Things like if-else and match are expressions, so there's no reason why you can't use one directly to initialize the is_male variable.

let is_male = match answer.as_str() {
  "Y" | "y" => true,
  "N" | "n" => false,
  other => {
    println!("Invalid input! Expected 'Y' or 'N' but got '{}'", other);
    return;
  }
};

You almost never see uninitialized variables like your let is_male: bool; in idiomatic Rust. Although the compiler will stop you when you try to use a variable that may not have been initialized, the of declaring a variable that gets initialized later on is largely unnecessary.

4 Likes

For the comparison the C compiler can happily accept this kind of code and sometimes prints both "You are a male." and "You are not a male.".

1 Like

People might be offended at being asked if they are male or otherwise not want to say. In this modern world gender is not a boolean. You should be prepared for that:

  enum Gender {Male, Female, Unspecified}

  let gender = match answer {
    "Y" | "y" => Gender::Male,
    "N" | "n" => Gender::Female,
    _ => Gender::Unspecified, 
  };

  if gender = Gender::male {
    ...
  }
  ...

If you mean gender to be boolean but allow the user the option to not specify then perhaps your program could express that by using the Option type:

  let is_male = match answer {
    "Y" | "y" => Some(true),
    "N" | "n" => Some(false),
    _ => None, 
  };

  if is_male == Some(true) {
    println!{"You are male."};
  }

Or perhaps you deem that not specifying ones gender is an error, in which case expressing that in your program with the Error type might be suitable:

  let is_male = match answer {
    "Y" | "y" => Ok(true),
    "N" | "n" => Ok(false),
    _ => Err("Gender unspecified"), 
  };

  if is_male == Ok(true) {
    println!{"You are male."};
  }
2 Likes

Or let's use an enum and Option:

  enum Gender {Male, Female}

  let answer = "Y";

  let gender = match answer {
    "Y" | "y" => Some(Gender::Male),
    "N" | "n" => Some(Gender::Female),
    _ => None, 
  };

  if let gender = Some(Gender::Male) {
    println!{"You are male."};
  }

I like this better because:

  1. Using the enum clearly states what it is talking about. Whereas 'true', 'false' do not.

  2. It caters for the unspecified case.

  3. I presume 'unspecified' is not an error.

2 Likes

I'm just trying to test the if-else statement but I get what you mean. Thank you for the advice!

1 Like

Yeah, sorry, I got a bit carried away there...

So after I test this code:

enum Gender {Male, Female, Others, Unspecified};
let mut answer = String::new();

println!("What is your gender? (Input 'M' for male, 'F' for female, 'O' for others and 'U' for unspecified.)");
io::stdin()
    .read_line(&mut answer)
    .expect("Failed to read line");

println!("{}", answer);


let gender = match answer.as_str() {
    "M" | "m" => Some(Gender::Male),
    "F" | "f" => Some(Gender::Female),
    "O" | "o" => Some(Gender::Others),
    "U" | "u" => Some(Gender::Unspecified),
    others => {
        println!("Invalid input! Expected 'M', 'F', 'O' or 'U' but got '{}'", others);
        return;
    }
};
if let gender = Some(Gender::Male) {
    println!{"You are male."};
}

When I allow user input, the match statement always prints Invalid input! Expected 'M', 'F', 'O' or 'U' but got {whatever}. But when I use the code you gave me, which does not allow user input, it worked fine. Why is this the case?

The .read_line() also put the newline character into the buffer. try match over the answer.trim() instead.

2 Likes

Thank you!