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
iolibrary from the standard library to handle input/output.
- Imports the
-
Main Function:
fn main() {- Entry point of the program, declaring a function with no parameters.
-
Creating a Mutable Variable:
let mut guess = String::new();letstatement: Declares a variable.mut: Makes the variable mutable.String::new(): Creates a new, emptyString.
-
Getting Input:
io::stdin() .read_line(&mut guess) .expect("Failed to read line");stdin(): Calls thestdinfunction to handle user input.read_linemethod: Reads input and appends it toguess.&mut guess: Passes a mutable reference to store the input in theguessstring.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.
-
-
ResultType:Resultis an enum with two variants:Ok: Indicates success, contains the result.Err: Indicates failure, contains error information.
- The
expectmethod:- If
Ok: Returns the value insideOk(number of bytes read). - If
Err: Causes the program to crash with the provided message.
- If
-
Unused
ResultWarning:-
If
expectisn’t used, Rust warns about the unusedResult: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.
- Uses the
-
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.
- Output:
-
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.
-
Update
Cargo.toml:[dependencies] rand = "0.8.5"- This specifies that the project depends on the
randcrate version0.8.5.
- This specifies that the project depends on the
-
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
randcrate and any of its dependencies, compiles them, and then compiles your project.
- Cargo fetches the
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 buildfor the first time, Cargo records the exact versions inCargo.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.0unless you modifyCargo.toml:
[dependencies] rand = "0.9.0" - Cargo will not update to version
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}");
}-
rand::Rng: Brings theRngtrait into scope, which defines methods for random number generation.- Traits: The
Rngtrait must be in scope to usegen_range. Traits will be covered in detail in Chapter 10.
- Traits: The
-
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 variantsLess,Greater, andEqual, used for comparison results.cmpmethod: Compares the guess withsecret_numberand returns a variant ofOrdering.matchexpression: Checks the result ofcmpand 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 trimmedStringinto a number.- Error handling: If the conversion fails (e.g., if the input isn’t a number),
expectwill display “Please type a number!” and crash.
Shadowing
- Rust allows shadowing: reusing a variable name (
guess) after converting its value from aStringto au32.- This avoids the need for multiple variable names (e.g.,
guess_strandguess).
- This avoids the need for multiple variable names (e.g.,
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
loopcreates 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 }'- Updating the
parse()Call:-
Modify the code to handle non-number input without crashing the program, using a
matchexpression instead ofexpect:let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; -
Explanation:
matchonparse():Ok(num): If the input is successfully parsed into a number,numis returned and assigned toguess.Err(_): If parsing fails (e.g., for non-numeric input), thecontinuestatement 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;
}
}
}
}