2.3.0. Key Points of This Section
In this section, you will learn:
- How to use
match - Shadowing
- Type casting
- The
Orderingtype
2.3.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 (covered in this section)
- If the guess is correct, print a congratulatory message and exit the program
2.3.2. Code Implementation
This is the code written up to the previous article:
use std::io;
use rand::Rng;
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");
println!("The number you guessed is:{}", guess);
println!("The secret number is: {}", range_number);
}
Step 1: Data Type Conversion
From the code, we can see that the variable guess is of string type, while range_number is of type i32 (a signed 32-bit integer type. The gen_range method returns a type consistent with the numeric type used in the range. Here, 1 and 101 are of type i32, so the return type is also i32. Data types will be discussed in the next chapter). Since the two variables have different types, they cannot be compared directly. We need to forcibly convert the string type into an integer type.
let guess:u32 = guess.trim().parse().expect("Please enter a number")
-
let guess:u32: Here we declare a variable namedguessof typeu32(an unsigned 32-bit integer type, which means it cannot represent negative numbers). But there is a question here: in the previous code (let mut guess = String::new();), we already declared a variable namedguess. Would this cause an error? The answer is no, because Rust allows you to use a new variable with the same name to hide the previous one. This is technically called shadowing (when the name of a variable, function, or type is redefined in the current scope, it hides the variable, function, or type with the same name from an outer scope). It allows the code to reuse the same variable name without declaring a new one. This feature will be explained in more detail in the next chapter.
Here is an example:
fn main(){
let a = 1;
println!("{}",a);
let a = "one";
println!("{}",a);
}
This will not cause the program to report an error, and it prints:
1
one
When the program executes the second line, a is assigned the value 1, so what gets printed is 1; on the fourth line, the program notices that a is being reused, so it discards the old value 1 and assigns "one" to a, so the next line prints one. This is shadowing.
=: assignmentguess.trim(): Here,guessrefers to the oldguess, which is a string representing the user's input. Because theread_line()method also records the Enter key pressed by the user, we need to use.trim(). The function of.trim()is to remove leading and trailing spaces and newline characters from the string (similar to.strip()in Python)..parse(): It can parse a string into some numeric type. A normal user input will definitely be a number between 1 and 100, and this number can fit into types such asi32,u32,i64, and so on. In that case, which type should it become after parsing? You have to tell Rust which type you want. That is why you need to explicitly declare the variable as typeu32when declaring it (that is, add:desired_typeafter the variable name, similar to static type annotation style in Python).
Of course, conversion may fail. For example, if the input isxyz, then it cannot be parsed into an integer. Rust smartly makes the return value of.parse()aResulttype (mentioned in Pt.1). This enum type has two values:OkandErr. If the conversion succeeds, the enum returnsOkplus the converted result; if it fails, it returnsErrplus the reason for the failure..expect(): This is a method of theResulttype (the same type as the return value of.parse()). If reading fails, then as described above,.parse()returnsErr, and after.expect()receives it, it directly triggerspanic!, terminating the current program and printing the error message insideexpect. Otherwise,.parse()returnsOk, and after.expect()receives it, it returns the attached value to the user, which means the converted value gets assigned to the variable.
Step 2: Comparing Numbers
After successfully converting the data type, we can compare the two numbers:
First declare the imported library at the beginning of the code:
use std::cmp::Ordering
This line means importing a type called Ordering from the std standard library. Ordering is an enum type with three variants (you can think of them as three possible values): Ordering::Less, Ordering::Greater, and Ordering::Equal, which represent less than, greater than, and equal to, respectively.
Then write the comparison code inside the main function:
match guess.cmp(&range_number){
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => println!("You win"),
}
-
guess.cmp(&range_number): There is a method called.cmp()onguess(cmpis short for compare), which compares the value before the.with the value inside(). The value before the.here isguess, and the value inside()here is the referenced value ofrange_number(&is the address-of operator and represents a reference). The return type of.cmp()isOrdering, which is the type imported above.
This also involves Rust's type inference. Here are two screenshots of the code in the IDE: one before writing the match part, and one after writing it. Pay attention to the line let range_number = rand::thread_rng().gen_range(1..101); (line 5):
You can see that before writing match, the IDE shows the data type of range_number as i32; after writing the match section, the IDE shows the data type of range_number as u32. Why is that? It is because in guess.cmp(&range_number) inside the match block, a size comparison is performed. Although the type of range_number is not explicitly defined, guess has already been explicitly defined as u32. Thanks to Rust compiler's powerful contextual type inference, the type of range_number is inferred as u32 based on the requirement of guess.cmp(&range_number). Without match, because Rust's default integer type is i32 and there are no other constraints requiring range_number to be some other type, the Rust compiler defines range_number as i32.
-
match: This is Rust's pattern matching expression. It allows us to decide what to do next based on the value of the enum typeOrderingreturned by the.cmp()method. Amatchexpression consists of multiple arms (also called branches). Each arm contains a pattern to match (used to match the input value, or you can think of it as the trigger condition) and a block of code to execute (when the pattern matches successfully, this block is executed). If the value aftermatch(in this program,guess.cmp(&range_number)) matches one of the arms, the program executes the code under that arm.
In this program, Ordering::Less, Ordering::Greater, and Ordering::Equal are the matching patterns, while println!("Too small"), println!("Too big"), and println!("You win") are their corresponding code blocks to execute. For example, if the value of guess is the same as range_number, then .cmp() returns Ordering::Equal; match finds that it matches the third arm and then executes the code block under that arm, namely println!("You win").
match performs matching in top-to-bottom order. In this program, it first matches Ordering::Less, then Ordering::Greater, and finally Ordering::Equal.
match will be explained in detail in the next chapter.
2.3.3. Code Result
Below is the complete code so far:
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 result is as follows:



Top comments (0)