DEV Community

Cover image for [Rust Guide] 2.4. Number Guessing Game Pt.4 - Repeated Prompting with Loop
SomeB1oody
SomeB1oody

Posted on

[Rust Guide] 2.4. Number Guessing Game Pt.4 - Repeated Prompting with Loop

2.4.0. Key Points of This Section

This is the final part of the number guessing game. The key points in this section are:

  • loop
  • break
  • continue
  • Flexible use of match
  • How to handle enum types

2.4.1. Game Objectives

  • Generate a random number between 1 and 100
  • Prompt the player to enter a guess
  • After the guess, the program will tell whether the guess is too big or too small
  • Keep asking repeatedly. If the guess is correct, print a congratulatory message and exit the program (covered in this section)

2.4.2. Code Implementation

Step 1: Implementing a Loop

In the previous code, we implemented the comparison for one input. Next, we need to implement repeated prompts and repeated comparisons until the user guesses the correct number.

Here is the code up to the section before this one:

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);  
}

Enter fullscreen mode Exit fullscreen mode

The part of the code that we need to execute repeatedly is the section from prompting, to comparison, to outputting the comparison result. In terms of code, that is:

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"),  
    }
Enter fullscreen mode Exit fullscreen mode

Rust provides the keyword loop for an infinite loop, and its structure is as follows:

loop {
    // Write the code here that you want to loop indefinitely
    // Write code here that wants to loop indefinitely
}
Enter fullscreen mode Exit fullscreen mode

You only need to put the code that needs to be executed repeatedly into this structure:

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"),  
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: The Condition for Exiting the Program

But note that at this point, the program can repeatedly ask for input, but it will keep asking forever and will never exit. Logically, after the user guesses correctly and the program prints the message, it should stop asking. This is where the keyword break, which is used to exit the loop, is needed. Just put it after the Ordering::Equal branch (the concept of branches was explained in the previous article, so it will not be repeated here). Remember: if you want to execute multiple lines of code in one branch, you need to wrap the code block in {}.

match guess.cmp(&range_number){  
    Ordering::Less => println!("Too small"),  
    Ordering::Greater => println!("Too big"),  
    Ordering::Equal => {  
        println!("You win");  
        break;  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Handling Invalid Input

There is still one problem with this code: if the user's input is not an integer, then .parse() will return Err, and once .expect() receives it, the program will terminate immediately. But the correct logic should be that if the input is invalid, the program should print an error message and then let the user enter again.

So what should we do? In 2.1 Number Guessing Game Pt.1 Single Guess, it was mentioned that the return value of .parse() is an enum type. If conversion succeeds, the return value is: Ok + the converted content; if it fails, the return value is: Err + the reason for failure. So where have we talked about enum types before? That's right—in the previous article, we mentioned the enum type Ordering, and in that article we used match to handle the cases of greater than, less than, and equal to. So here, we can also use match to handle the return value of .parse(), performing different operations in different cases. Specifically: if the conversion succeeds, continue executing; if it fails, skip the following code and execute the next loop iteration. In Rust, the keyword for skipping the current iteration of a loop is continue, just like in other languages.

So how exactly should the code be changed? Replace this line:

let guess:u32 = guess.trim().parse().expect("Please enter a number");
Enter fullscreen mode Exit fullscreen mode

with:

let guess:u32 = match guess.trim().parse() {  
    Ok(num) => num,  
    Err(_) => continue,  
};
Enter fullscreen mode Exit fullscreen mode
  • Ok(num) => num: This branch handles the case where the conversion succeeds, that is, when the return value is Ok plus the converted content. Ok is one variant of this enum type. Inside the () after Ok is the converted content carried by the enum (u32). Writing num inside the () here means binding the converted content to num. The value of num will be passed as the result of the match expression and then finally assigned to guess.

  • Err(_) => continue: This branch is used to handle the case where the conversion fails, that is, when the return value is Err plus the reason for failure. Err is a variant of the enum type. Inside the () after Err is the reason for the failure carried by the enum (&str). Writing _ inside the () means that we do not care about the specific error message; we only need to know that it is Err.

Using match here instead of .expect() to handle errors is a common Rust approach to error handling.

2.4.3. Code Result

Below 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(_) => 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);  
}
Enter fullscreen mode Exit fullscreen mode

Result:

Top comments (0)