Skip to Content
RustBooksThe Rust BookCh 2. The guessing game

Ch 2. The guessing game

Functionality: Asks the player for input, processes it, and displays the input.

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }
  • Bringing Libraries into Scope:

    use std::io;
    • Imports the io library from the standard library to handle input/output.
  • Main Function:

    fn main() {
    • Entry point of the program, declaring a function with no parameters.
  • Creating a Mutable Variable:

    let mut guess = String::new();
    • let statement: Declares a variable.
    • mut: Makes the variable mutable.
    • String::new(): Creates a new, empty String.
  • Getting Input:

    io::stdin() .read_line(&mut guess) .expect("Failed to read line");
    • stdin(): Calls the stdin function to handle user input.
    • read_line method: Reads input and appends it to guess.
    • &mut guess: Passes a mutable reference to store the input in the guess string.
    • expect: Handles potential errors if reading the input fails.
  • Handling Potential Failure with Result:

    .expect("Failed to read line");
    • This part ensures the program handles potential errors when reading input.

    • The full line can be written as:

      io::stdin().read_line(&mut guess).expect("Failed to read line");
      • However, splitting the line enhances readability.
  • Result Type:

    • Result is an enum with two variants:
      • Ok: Indicates success, contains the result.
      • Err: Indicates failure, contains error information.
    • The expect method:
      • If Ok: Returns the value inside Ok (number of bytes read).
      • If Err: Causes the program to crash with the provided message.
  • Unused Result Warning:

    • If expect isn’t used, Rust warns about the unused Result:

      warning: unused `Result` that must be used
    • To ignore the result:

      let _ = io::stdin().read_line(&mut guess);
  • Printing Messages:

    println!("Guess the number!"); println!("Please input your guess.");
    • Uses the println! macro to display prompts for the game and user input.
  • Printing Values with println! Placeholders:

    println!("You guessed: {}", guess);
    • {}: Placeholder for a value, e.g., a variable.

    • You can print multiple values or expressions:

      let x = 5; let y = 10; println!("x = {x} and y + 2 = {}", y + 2);
      • Output: x = 5 and y + 2 = 12.

Generating a Secret Number

  • To generate a secret number between 1 and 100, we need the rand crate. Rust’s standard library doesn’t include random number generation, so we add this external crate.
  1. Update Cargo.toml:

    [dependencies] rand = "0.8.5"
    • This specifies that the project depends on the rand crate version 0.8.5.
  2. Build the Project:

    $ cargo build Downloaded rand v0.8.5 Compiling rand v0.8.5 Finished dev [unoptimized + debuginfo] target(s) in 2.53s
    • Cargo fetches the rand crate and any of its dependencies, compiles them, and then compiles your project.

How Cargo Manages Dependencies

  • Crates.io: Rust’s registry for open-source projects.
  • Cargo.lock:
    • Ensures reproducible builds by locking dependency versions.
    • When you run cargo build for the first time, Cargo records the exact versions in Cargo.lock.
    • Future builds will use the same versions unless explicitly updated. Cargo uses this to manage compatible versions of crates.

Updating Dependencies

  • To update a crate:

    $ cargo update Updating rand v0.8.5 -> v0.8.6
    • Cargo will not update to version 0.9.0 unless you modify Cargo.toml:
    [dependencies] rand = "0.9.0"

Modified from previous snippet

use std::io; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {guess}"); }
  1. rand::Rng: Brings the Rng trait into scope, which defines methods for random number generation.

    • Traits: The Rng trait must be in scope to use gen_range. Traits will be covered in detail in Chapter 10.
  2. Generating the Secret Number:

    • rand::thread_rng(): Provides a random number generator seeded by the operating system, local to the current thread.
    • gen_range(1..=100):
      • Generates a random number between 1 and 100 (inclusive).
    $ cargo run Guess the number! The secret number is: 7 Please input your guess. 4 You guessed: 4

Comparing the Guess to the Secret Number

use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { // --snip-- println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }
  • std::cmp::Ordering: An enum with variants Less, Greater, and Equal, used for comparison results.
  • cmp method: Compares the guess with secret_number and returns a variant of Ordering.
  • match expression: Checks the result of cmp and executes the corresponding code based on the outcome:
    • Ordering::Less: Prints “Too small!”
    • Ordering::Greater: Prints “Too big!”
    • Ordering::Equal: Prints “You win!”

Error: Mismatched Types

  • Reason: Rust cannot compare a String (user input) to a number (secret_number).
    • The error:

      error[E0308]: mismatched types expected reference `&String`, found reference `&{integer}`

Fix: Convert the Guess to a Number

let guess: u32 = guess.trim().parse().expect("Please type a number!");
  • guess.trim(): Removes any leading or trailing whitespace (e.g., newline characters).
  • parse(): Converts the trimmed String into a number.
  • Error handling: If the conversion fails (e.g., if the input isn’t a number), expect will display “Please type a number!” and crash.

Shadowing

  • Rust allows shadowing: reusing a variable name (guess) after converting its value from a String to a u32.
    • This avoids the need for multiple variable names (e.g., guess_str and guess).

Allowing Multiple Guesses with Looping

loop { println!("Please input your guess."); // --snip-- match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } }
  • Purpose: The loop creates an infinite loop, allowing the user to guess multiple times until they win.
  • Indentation: Ensure code inside the loop is indented for clarity.
  • break: Exits the loop when the correct guess is made, thus ending the game.

Handling Quitting on Non-Numeric Input

  • Current Behavior: Non-numeric input (e.g., “quit”) causes the program to crash due to parse() failing.
thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }'
  1. Updating the parse() Call:
    • Modify the code to handle non-number input without crashing the program, using a match expression instead of expect:

      let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, };
    • Explanation:

      • match on parse():
        • Ok(num): If the input is successfully parsed into a number, num is returned and assigned to guess.
        • Err(_): If parsing fails (e.g., for non-numeric input), the continue statement skips the current iteration and asks for another guess.
      • This change prevents the program from crashing on invalid input and allows the user to guess again.

Final Code

use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }
Last updated on