Programming a Guessing Game π²
In the previous chapter, we learned about the basics of Rust. In this chapter, we will learn about the basics of Rust by building a simple guessing game.
Setting up the project
We will use the Cargo package manager to create a new project called guessing_game.
We can create a new project using the following command:
cargo new guessing_game
This creates a new project called guessing_game in the current directory.
The Cargo package manager creates a new directory with the following structure:
guessing_game
βββ Cargo.toml
βββ src
    βββ main.rs
The Cargo.toml file contains the metadata of the project. The src directory contains the source
code of the project. The main.rs file contains the main function of the project. As we saw
in the previous chapter, the Cargo package
manager uses the main.rs file as the entry point of the project. The Cargo package manager uses
the Cargo.toml file to manage the dependencies of the project.
Guessing game code
The full code for the guessing game, which is in the main.rs file, looks as follows:
use std::io;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=101);
    loop {
        println!("Please input your guess.");
        let mut guess = String::new();
        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");
        let guess: i32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("This is not expected. Please enter an integer");
                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! You are the guessing game champion!");
                break;
            },
        }
    }
}
Much of the code looks like mumbo jumbo for now, but we will break it down in pieces.
Breakdown of the above code
The use keyword is used to import the io module from the standard library. The io module
contains the stdin function which is used to read the input from the user. The stdin function
returns an instance of the Stdin type. The Stdin type implements the Read trait. The Read
trait defines the read_line method which is used to read a line from the Stdin type.
NOTE: Traits are similar to interfaces in other languages. They define the methods that a type
must implement. We will learn more about traits in a later chapter.
Points to remember:
The
mainfunction is the entry point of the program.The
println!macro is used to print the string to the standard output. Theprintln!macro is similar to theprintffunction in C or theprintfunction in Python. Theprintln!macro is a macro because it is prefixed with an exclamation mark. We will learn more about macros in a later chapter.The
letkeyword is used to create a new variable.The
mutkeyword is used to make the variable mutable.The
String::newfunction is used to create a new empty string. TheStringtype is a string type provided by the standard library. TheStringtype is a growable, UTF-8 encoded string.However, we still need a way to read the input from the user. The
read_linemethod takes the input from the user and stores it in theguessvariable. Theread_linemethod takes the input as a mutable reference.The
&symbol is used to create a reference. The&mutsymbol is used to create a mutable reference.The
read_linemethod returns aResulttype. TheResulttype is anenumwhich has two variants:OkandErr. TheOkvariant indicates that the operation was successful. TheErrvariant indicates that the operation failed. Theexpectmethod is used to handle theErrvariant. Theexpectmethod takes a string as an argument. If theResulttype isOk, theexpectmethod returns the value inside theOkvariant. If theResulttype isErr, theexpectmethod prints the string passed to it and exits the program. If we don’t call theexpectmethod, the program will compile but will throw a warning.
$ cargo build
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
warning: unused `Result` that must be used
  --> src/main.rs:10:5
   |
10 |     io::stdin().read_line(&mut guess);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: this `Result` may be an `Err` variant, which should be handled
warning: `guessing_game` (bin "guessing_game") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
The Rust compiler is smart enough to detect that the Result type returned by the read_line
method is not being used. The Rust compiler throws a warning to let us know that we are not
handling the Err variant of the Result type.
Phew! That was a lot of information. Take a look at the code again and make sure we can understand the code with the new information that we have learned.
Building and Running the code
We can run the code using the following command:
cargo run
Key takeaways:
cargo runwill build and run the code, whilecargo buildwill only build an executable in thetarget/debugdirectory.cargo runis useful when we are developing the code.cargo runwill compile the code and run the executable every time we make a change to the code.cargo buildis useful when we are deploying the code.cargo buildwill compile the code and create an executable.
Once run, the code will print the following output:
$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
5
You guessed: 5
We can see that we can now take an input from the user. However, we still need to generate a random
number with which the comparison for the user input has to be done. This random number generator functionality
can be implemented smoothly by using cargo.
Generating a random number
With cargo, we have the option to get crates. Crates are packages of Rust code that we can
use in our project. We can get a crate by adding the crate name and the version number to the
Cargo.toml file. The Cargo package manager will download the crate and add it to the Cargo.lock
For our case, we need to add the rand crate to the Cargo.toml file. The rand crate is a
crate that provides random number generation functionality.
To add the rand crate to the Cargo.toml file, we need to add the following line to the Cargo.toml file:
[dependencies]
rand = "0.8.5"
The documentation of the rand crate can be found
here.
If you build the project now with cargo build, we should see that the rand create is being fetched
and added to the Cargo.lock file.
$ cargo build
    Updating crates.io index
 Downloading crates ...
  Downloaded rand v0.8.5
  Downloaded 1 crate (1.1 MB) in 0.75s
   Compiling rand v0.8.5
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 1.88s
However, if you run it again, we will not see the above output. cargo will check the Cargo.lock file
to see if the dependencies have changed. If the dependencies have not changed, cargo will not fetch
the dependencies again. This is useful when we are deploying the code, since we can be sure that the
dependencies will not change when we deploy the code, and we will deploy the code with the same
dependencies that we were using while developing the code.
Generating Random Number for our game
Now that we have the rand crate, we can use it to generate a random number. We can use the gen_range
method of the rand crate to generate a random number. The gen_range method takes two arguments:
the lower bound and the upper bound. The gen_range method will generate a random number between
the lower bound and the upper bound.
use std::io;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=101);
    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);
}
If you run the code now, you will see that the code will generate a random number between 1 and 101, and will print it to the console.
$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 1.02s
     Running `target/debug/guessing_game`
Guess the number!
The secret number is: 42
Please input your guess.
5
You guessed: 5
Now we see that a random number is being generated. However, we still need to compare the user input with the random number. We will do this in the next section.
Comparing the user input with the random number
We can compare the user input with the random number using the cmp method.
Points to remeber:
- The 
cmpmethod takes a reference to the value that we want to compare with. - The 
cmpmethod returns anOrderingtype. - The 
Orderingtype is anenumthat can have three values:Less,Greater, andEqual. Thecmpmethod compares the value that we are calling the method on with the value that we pass as an argument to thecmpmethod. - If the value that we are calling the method on is less than the value
that we pass as an argument to the 
cmpmethod, thecmpmethod will return theLessvariant of theOrderingtype. If the value that we are calling the method on is greater than the value that we pass as an argument to thecmpmethod, thecmpmethod will return theGreatervariant of theOrderingtype. If the value that we are calling the method on is equal to the value that we pass as an argument to thecmpmethod, thecmpmethod will return theEqualvariant of theOrderingtype. - We can use the 
matchexpression to handle theOrderingtype returned by thecmpmethod. - The 
matchexpression is similar to theswitchstatement in other languages. - The 
matchexpression takes a value as an argument and compares the value with the patterns that we specify in thematchexpression. If the value matches the pattern, the code that is associated with the pattern will be executed. If the value does not match any of the patterns, thematchexpression will throw an error. 
use std::io;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=101);
    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);
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win! You are the guessing game champion!"),
    }
}
If you run the code now, you will see that the code will not compile.
Points to remember:
The reason for this is that we are trying to compare a String type with an i32 type. We can
fix this by converting the String type to an i32 type. We can do this by using the trim method to remove the newline character
from the String type, and then using the parse method to convert the String type to an i32
type.
use std::io;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=101);
    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: i32 = 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! You are the guessing game champion!"),
    }
}
If you run the code now, you will see that the code will compile and run.
However, we can still type an alphabet and get away with it. We can fix the input to be only numbers
by using the Result type returned by the parse method. The Result is of type enum and can have
two values: Ok and Err.
Points to remember:
- The 
Okvariant of theResulttype means that the operation was successful - The 
Errvariant of theResulttype means that the operation failed. - The 
parsemethod will return theOkvariant of theResulttype if the conversion was successful, and will return theErrvariant of theResulttype if the conversion failed. - We can use the 
matchexpression to handle theResulttype returned by theparsemethod. If theparsemethod returns theOkvariant of theResulttype, we will assign the value that is inside theOkvariant to theguessvariable. If theparsemethod returns theErrvariant of theResulttype, we will print the error message that is inside theErrvariant to the console. 
With these points, we can change the code as follows:
use std::io;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=101);
    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: i32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => {
            println!("This is not expected. Please enter an integer");
            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! You are the guessing game champion!"),
    }
}
If you run the code now, you will see that the code will compile and run. However, if you enter a
non-numeric value, the code will not panic. Instead, the code will print the error message that we
specified in the Err variant of the Result type. We handled the panic using the Err variant of
the Result type.
Looping until correct number is guessed
We can use loop to keep the program running until the correct number is guessed. This can be done as
follows:
use std::io;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=101);
    println!("The secret number is: {}", secret_number);
    loop {
        println!("Please input your guess.");
        let mut guess = String::new();
        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");
        let guess: i32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("This is not expected. Please enter an integer");
                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! You are the guessing game champion!");
                break;
            },
        }
    }
}
Points to remember:
We just use loop keyword to create an infinite loop. We can use break keyword to break out of
the loop. We can also use continue keyword to skip the rest of the loop and start the next
iteration of the loop.
Removing the secret number message
We just have to remove the secret number generation part from the code. Once we remove the secret number generation part from the code, we will not be able to print the secret number to the console.
The final script should then look like this:
use std::io;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=101);
    loop {
        println!("Please input your guess.");
        let mut guess = String::new();
        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");
        let guess: i32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("This is not expected. Please enter an integer");
                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! You are the guessing game champion!");
                break;
            },
        }
    }
}
