Comparing the Guess to the Secret Number
Now that we have user input and a random number, we can compare them. That step is shown in Listing 2-4. Note that this code won't compile just yet, as we will explain.
Filename: src/main.rs
use rand::Rng;
1 use std::cmp::Ordering;
use std::io;
fn main() {
--snip--
println!("You guessed: {guess}");
2 match guess.3 cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
Listing 2-4: Handling the possible return values of comparing two numbers
First we add another use
statement [1], bringing a type called std::cmp::Ordering
into scope from the standard library. The Ordering
type is another enum and has the variants Less
, Greater
, and Equal
. These are the three outcomes that are possible when you compare two values.
Then we add five new lines at the bottom that use the Ordering
type. The cmp
method [3] compares two values and can be called on anything that can be compared. It takes a reference to whatever you want to compare with: here it's comparing guess
to secret_number
. Then it returns a variant of the Ordering
enum we brought into scope with the use
statement. We use a match
expression [2] to decide what to do next based on which variant of Ordering
was returned from the call to cmp
with the values in guess
and secret_number
.
A match
expression is made up of arms. An arm consists of a pattern to match against, and the code that should be run if the value given to match
fits that arm's pattern. Rust takes the value given to match
and looks through each arm's pattern in turn. Patterns and the match
construct are powerful Rust features: they let you express a variety of situations your code might encounter and they make sure you handle them all. These features will be covered in detail in Chapter 6 and Chapter 18, respectively.
Let's walk through an example with the match
expression we use here. Say that the user has guessed 50 and the randomly generated secret number this time is 38.
When the code compares 50 to 38, the cmp
method will return Ordering::Greater
because 50 is greater than 38. The match
expression gets the Ordering::Greater
value and starts checking each arm's pattern. It looks at the first arm's pattern, Ordering::Less
, and sees that the value Ordering::Greater
does not match Ordering::Less
, so it ignores the code in that arm and moves to the next arm. The next arm's pattern is Ordering::Greater
, which does match Ordering::Greater
! The associated code in that arm will execute and print Too big!
to the screen. The match
expression ends after the first successful match, so it won't look at the last arm in this scenario.
However, the code in Listing 2-4 won't compile yet. Let's try it:
$ cargo build
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
error[E0308]: mismatched types
--> src/main.rs:22:21
|
22 | match guess.cmp(&secret_number) {
| ^^^^^^^^^^^^^^ expected struct `String`, found integer
|
= note: expected reference `&String`
found reference `&{integer}`
The core of the error states that there are mismatched types. Rust has a strong, static type system. However, it also has type inference. When we wrote let mut guess = String::new()
, Rust was able to infer that guess
should be a String
and didn't make us write the type. The secret_number
, on the other hand, is a number type. A few of Rust's number types can have a value between 1 and 100: i32
, a 32-bit number; u32
, an unsigned 32-bit number; i64
, a 64-bit number; as well as others. Unless otherwise specified, Rust defaults to an i32
, which is the type of secret_number
unless you add type information elsewhere that would cause Rust to infer a different numerical type. The reason for the error is that Rust cannot compare a string and a number type.
Ultimately, we want to convert the String
the program reads as input into a real number type so we can compare it numerically to the secret number. We do so by adding this line to the main
function body:
Filename: src/main.rs
use std::io;
use rand::Rng;
use std::cmp::Ordering;
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");
let guess: u32 = guess
.trim()
.parse()
.expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
We create a variable named guess
. But wait, doesn't the program already have a variable named guess
? It does, but helpfully Rust allows us to shadow the previous value of guess
with a new one. Shadowing lets us reuse the guess
variable name rather than forcing us to create two unique variables, such as guess_str
and guess
, for example. We'll cover this in more detail in Chapter 3, but for now, know that this feature is often used when you want to convert a value from one type to another type.
We bind this new variable to the expression guess.trim().parse()
. The guess
in the expression refers to the original guess
variable that contained the input as a string. The trim
method on a String
instance will eliminate any whitespace at the beginning and end, which we must do to be able to compare the string to the u32
, which can only contain numerical data. The user must press enter to satisfy read_line
and input their guess, which adds a newline character to the string. For example, if the user types 5
and presses enter, guess
looks like this: 5\n
. The \n
represents "newline." (On Windows, pressing enter results in a carriage return and a newline, \r\n
.) The trim
method eliminates \n
or \r\n
, resulting in just 5
.
The parse
method on strings converts a string to another type. Here, we use it to convert from a string to a number. We need to tell Rust the exact number type we want by using let guess: u32
. The colon (:
) after guess
tells Rust we'll annotate the variable's type. Rust has a few built-in number types; the u32
seen here is an unsigned, 32-bit integer. It's a good default choice for a small positive number. You'll learn about other number types in Chapter 3.
Additionally, the u32
annotation in this example program and the comparison with secret_number
means Rust will infer that secret_number
should be a u32
as well. So now the comparison will be between two values of the same type!
The parse
method will only work on characters that can logically be converted into numbers and so can easily cause errors. If, for example, the string contained A
ð%
, there would be no way to convert that to a number. Because it might fail, the parse
method returns a Result
type, much as the read_line
method does (discussed earlier in "Handling Potential Failure with Result"). We'll treat this Result
the same way by using the expect
method again. If parse
returns an Err
Result
variant because it couldn't create a number from the string, the expect
call will crash the game and print the message we give it. If parse
can successfully convert the string to a number, it will return the Ok
variant of Result
, and expect
will return the number that we want from the Ok
value.
Let's run the program now:
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
76
You guessed: 76
Too big!
Nice! Even though spaces were added before the guess, the program still figured out that the user guessed 76. Run the program a few times to verify the different behavior with different kinds of input: guess the number correctly, guess a number that is too high, and guess a number that is too low.
We have most of the game working now, but the user can make only one guess. Let's change that by adding a loop!