2.4 Number Guessing Game Pt.4 - Repeated Prompting with Loop
2.4.0 What You Will Learn
This is the final part of the number guessing game. In this chapter, you will learn:
- The
looploop breakcontinue- Flexible use of
match - How to handle enums
2.4.1 Game Goal
- Generate a random number between 1 and 100
- Prompt the player to enter a guess
- After the guess, the program will tell the player whether the guess is too large or too small
- Repeatedly prompt the player. If the guess is correct, print a celebration message and exit the program (covered in this chapter)
2.4.2 Code Implementation
Step 1: Implement the loop
In the previous code, we implemented a single round of input and comparison. Next, we need to make the program ask and compare repeatedly until the user guesses the correct number.
Here is the code up to 2.3 Number Guessing Game Pt.3 - Comparing Input and Random Number:
use std::io;
use rand::Rng;
use std::cmp::Ordering;
fn main() {
let range_number = rand::thread_rng().gen_range(1..101);
println!("Number Guessing Game");
println!("Guess a number");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Could not read the line");
let guess: u32 = guess.trim().parse().expect("Please enter a number");
println!("The number you guessed is:{}", guess);
match guess.cmp(&range_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => println!("You win"),
}
println!("The secret number is: {}", range_number);
}
The code we need to repeat is the part from prompting the user to comparing the guess and printing the result:
#![allow(unused)]
fn main() {
println!("Guess a number");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Could not read the line");
let guess: u32 = guess.trim().parse().expect("Please enter a number");
println!("The number you guessed is:{}", guess);
match guess.cmp(&range_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => println!("You win"),
}
}
Rust provides the keyword loop for an infinite loop. Its structure is:
#![allow(unused)]
fn main() {
loop {
// Write code here that wants to loop indefinitely
}
}
Just place the code that needs to be repeated inside this structure:
#![allow(unused)]
fn main() {
loop {
println!("Guess a number");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Could not read the line");
let guess: u32 = guess.trim().parse().expect("Please enter a number");
println!("The number you guessed is:{}", guess);
match guess.cmp(&range_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => println!("You win"),
}
}
}
Step 2: Condition for exiting the program
However, note that although this gives us repeated prompting, the program will keep asking forever and never exit. Logically, once the user guesses correctly and the program prints the congratulatory message, it should stop asking. This is where the keyword break for breaking out of a loop is needed. Put it after the Ordering::Equal arm (the concept of arms was explained in 2.3 Number Guessing Game Pt.3 - Comparing Input and Random Number, so I will not repeat it here). Also remember that if an arm needs to execute multiple lines of code, wrap the code block in {}.
#![allow(unused)]
fn main() {
match guess.cmp(&range_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win");
break;
}
}
}
Step 3: Handling invalid input
This code still has another problem: if the user’s input is not an integer, .parse() returns Err, and .expect() immediately terminates the program. The correct behavior is to print an error message and then let the user try again.
What should we do? In 2.1 Number Guessing Game Pt.1 - One Guess, we saw that read_line() returns a Result enum. In 2.3 Number Guessing Game Pt.3 - Comparing Input and Random Number, .parse() likewise returns a Result: if conversion succeeds, the return value is Ok plus the converted content; if it fails, the return value is Err plus the reason for failure. So where did we use an enum with match before? That’s right — in 2.3 Number Guessing Game Pt.3 - Comparing Input and Random Number, we introduced the Ordering enum. There, we used match to handle the greater-than, less-than, and equal cases. Here, we can also use match to handle the return value of .parse() and perform different actions for different cases: if conversion succeeds, continue execution; if it fails, print an error message, skip the rest of the loop body, and start the next iteration. The keyword for skipping the current loop iteration in Rust is the same as in other languages: continue.
How do we change the code? We replace let guess: u32 = guess.trim().parse().expect("Please enter a number"); with:
#![allow(unused)]
fn main() {
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Please enter a number");
continue;
}
};
}
Ok(num) => num: this branch handles the case where conversion succeeds. The return value isOkplus the converted value.Okis a variant of this enum, and the value inside the parentheses afterOkis the converted content (u32). Writingnumhere means binding the converted content tonum, andnumis then passed to thematchexpression as the result and ultimately assigned toguess.Err(_) => { ... continue; }: this branch handles the case where conversion fails.Erris the enum variant, and the value inside the parentheses afterErris the error value (for parsing an integer, aParseIntError). The_means we do not care about the error details; we only need to know that it isErr. We print a short message and thencontinueto the next loop iteration.
Using match instead of .expect() to handle errors is a common Rust pattern.
2.4.3 Result
Here is the complete code:
use std::io;
use rand::Rng;
use std::cmp::Ordering;
fn main() {
let range_number = rand::thread_rng().gen_range(1..101);
println!("Number Guessing Game");
loop {
println!("Guess a number");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Could not read the line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Please enter a number");
continue;
},
};
println!("The number you guessed is:{}", guess);
match guess.cmp(&range_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win");
break;
},
}
}
println!("The secret number is: {}", range_number);
}
Result (one local run; your secret number and path to winning will differ):
Number Guessing Game
Guess a number
10
The number you guessed is:10
Too small
Guess a number
50
The number you guessed is:50
Too small
Guess a number
100
The number you guessed is:100
You win
The secret number is: 100