Contents

Ch.2 — Programming a Guessing Game

Chapter 2

Programming a Guessing Game


Let’s jump into Rust by working through a hands-on project together. This chapter introduces a few common Rust concepts by showing how to use them in a real program: let, match, methods, associated functions, external crates, and more. The following chapters explore these ideas in more detail; here you just practice the fundamentals.

We implement a classic beginner problem, a guessing game. The program generates a random integer between 1 and 100, then prompts the player to enter a guess. After each guess it says whether the guess is too low or too high. If the guess is correct, the game prints a congratulatory message and exits.

  • 2.1 Setting Up a New Project
  • 2.2 Processing a Guess
  • 2.3 Generating a Secret Number
  • 2.4 Comparing the Guess to the Secret Number
  • 2.5 Allowing Multiple Guesses with Looping

The Rust Programming Language · Ch.2 Programming a Guessing Game

1

Chapter 2 · 2.1 Setting Up a New Project

Creating the guessing_game project

Go to the projects directory you created in Chapter 1 and make a new project using Cargo:

$ cargo new guessing_game

$ cd guessing_game

The first command, cargo new, takes the name of the project (guessing_game) as its first argument. The second command changes to the new project’s directory.

Look at the generated Cargo.toml file:

Cargo.toml

[package]

name = "guessing_game"

version = "0.1.0"

edition = "2024"

 

[dependencies]

As in Chapter 1, cargo new generates a “Hello, world!” program:

src/main.rs

fn main() {

    println!("Hello, world!");

}

The Rust Programming Language · Ch.2 Programming a Guessing Game

2

Chapter 2 · 2.1 Setting Up a New Project

Compiling and running in one step

Compile this “Hello, world!” program and run it in the same step using the cargo run command:

$ cargo run

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s

     Running `target/debug/guessing_game`

Hello, world!

The run command comes in handy when you need to rapidly iterate on a project, as we’ll do in this game, quickly testing each iteration before moving on to the next one.

Reopen the src/main.rs file. You’ll be writing all the code of the game in this file.

The Rust Programming Language · Ch.2 Programming a Guessing Game

3

Chapter 2 · 2.2 Processing a Guess

Getting a guess from the user

The first part of the program asks for user input, processes that input, and checks that it is in the expected form. To start, we allow the player to input a guess: enter Listing 2.1 into src/main.rs.

Listing 2.1: Code that gets a guess from the user and prints it — src/main.rs

use std::io;

 

fn main() {

    println!("Guess the 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}");

}

The Rust Programming Language · Ch.2 Programming a Guessing Game

4

Chapter 2 · 2.2 Processing a Guess

Line by line: io, the prelude, main and println!

To get input and print output, bring the io library from the standard library, std, into scope:

use std::io;

Definition 2.1 (Prelude). The set of items defined in the standard library that Rust brings into the scope of every program by default. Everything in it is listed in the standard library documentation.

If a type you want to use isn’t in the prelude, bring it into scope explicitly with a use statement. Using the std::io library provides a number of useful features, including the ability to accept user input.

fn main() {

As in Chapter 1, main is the entry point. fn declares a new function; () indicates there are no parameters; { starts the body.

println!("Guess the number!");

 

println!("Please input your guess.");

println! is a macro that prints a string to the screen. These lines print a prompt stating what the game is and requesting input.

The Rust Programming Language · Ch.2 Programming a Guessing Game

5

Chapter 2 · 2.2 Processing a Guess

Storing values with variables

Next, create a variable to store the user input. There’s a lot going on in this little line:

let mut guess = String::new();

Definition 2.2 (Variable). A name created with the let statement and bound to a value. In Rust, variables are immutable by default: once the variable is given a value, the value won’t change (Chapter 3, “Variables and Mutability”).

Another example: this line creates a new variable named apples and binds it to the value 5.

let apples = 5;

To make a variable mutable, add mut before the variable name:

let apples = 5; // immutable

let mut bananas = 5; // mutable

Remark. The // syntax starts a comment that continues until the end of the line. Rust ignores everything in comments. Chapter 3 discusses comments in more detail.

The Rust Programming Language · Ch.2 Programming a Guessing Game

6

Chapter 2 · 2.2 Processing a Guess

Binding guess to a new, empty String

let mut guess = String::new();

let mut guess introduces a mutable variable named guess. The equal sign (=) tells Rust we want to bind something to the variable now. On its right is the value guess is bound to: the result of calling String::new, a function that returns a new instance of a String.

String is a string type provided by the standard library that is a growable, UTF-8 encoded bit of text.

Definition 2.3 (Associated function). A function that’s implemented on a type, here String. The :: syntax in the ::new line indicates that new is an associated function of the String type.

This new function creates a new, empty string. You’ll find a new function on many types, because it’s a common name for a function that makes a new value of some kind.

In full, the line has created a mutable variable that is currently bound to a new, empty instance of a String. Whew!

The Rust Programming Language · Ch.2 Programming a Guessing Game

7

Chapter 2 · 2.2 Processing a Guess

Receiving user input with stdin and read_line

We included the input/output functionality with use std::io; on the first line. Now we call the stdin function from the io module, which lets us handle user input:

io::stdin()

    .read_line(&mut guess)

    .expect("Failed to read line");

Without use std::io; at the beginning of the program, we could still call the function by writing std::io::stdin. The stdin function returns an instance of std::io::Stdin, a type that represents a handle to the standard input for your terminal.

Next, .read_line(&mut guess) calls the read_line method on the standard input handle to get input from the user. The argument &mut guess tells it what string to store the input in: the full job of read_line is to take whatever the user types into standard input and append it to that string (without overwriting its contents). The string argument needs to be mutable so that the method can change the string’s content.

The Rust Programming Language · Ch.2 Programming a Guessing Game

8

Chapter 2 · 2.2 Processing a Guess

References: why &mut guess

.read_line(&mut guess)

Definition 2.4 (Reference). An argument marked with &. A reference gives you a way to let multiple parts of your code access one piece of data without needing to copy that data into memory multiple times.

  • References are a complex feature, and one of Rust’s major advantages is how safe and easy it is to use them.
  • You don’t need to know a lot of those details to finish this program; Chapter 4 explains references more thoroughly.
  • For now, all you need to know is that, like variables, references are immutable by default.
  • Hence you need to write &mut guess rather than &guess to make it mutable.

The Rust Programming Language · Ch.2 Programming a Guessing Game

9

Chapter 2 · 2.2 Processing a Guess

Handling potential failure with Result

We’re still working on the same line of code. The third line of text below is still part of a single logical line of code; the next part is the expect method:

io::stdin()

    .read_line(&mut guess)

    .expect("Failed to read line");

We could have written this code as:

io::stdin().read_line(&mut guess).expect("Failed to read line");

However, one long line is difficult to read, so it’s best to divide it. It’s often wise to introduce a newline and other whitespace to help break up long lines when you call a method with the .method_name() syntax.

The Rust Programming Language · Ch.2 Programming a Guessing Game

10

Chapter 2 · 2.2 Processing a Guess

The Result enum and its expect method

read_line puts whatever the user enters into the string we pass to it, but it also returns a Result value.

Definition 2.5 (Enumeration). A type that can be in one of multiple possible states, often called an enum; each possible state is a variant. Result is an enum (enums are covered in Chapter 6).

The purpose of Result types is to encode error-handling information. Result’s variants are:

  • Ok — the operation was successful; it contains the successfully generated value.
  • Err — the operation failed; it contains information about how or why it failed.

Like values of any type, a Result has methods, such as expect. If the Result is an Err value, expect causes the program to crash and displays the message passed to it; an Err from read_line would likely come from the underlying operating system. If it is an Ok value, expect takes the value Ok is holding and returns just that value for you to use — here, the number of bytes in the user’s input.

The Rust Programming Language · Ch.2 Programming a Guessing Game

11

Chapter 2 · 2.2 Processing a Guess

The warning when expect is missing

If you don’t call expect, the program will compile, but you’ll get 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: this `Result` may be an `Err` variant, which should be handled

   = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default

help: use `let _ = ...` to ignore the resulting value

   |

10 |     let _ = io::stdin().read_line(&mut guess);

   |     +++++++

 

warning: `guessing_game` (bin "guessing_game") generated 1 warning

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s

The Rust Programming Language · Ch.2 Programming a Guessing Game

12

Chapter 2 · 2.2 Processing a Guess

Handling the warning; println! placeholders

Rust warns that you haven’t used the Result value returned from read_line, indicating that the program hasn’t handled a possible error.

The right way to suppress the warning is to actually write error-handling code, but in our case we just want to crash the program when a problem occurs, so we can use expect. Recovering from errors is the subject of Chapter 9.

Printing values with println! placeholders

Aside from the closing curly bracket, there’s only one more line to discuss in the code so far:

println!("You guessed: {guess}");

This line prints the string that now contains the user’s input. The {} set of curly brackets is a placeholder: think of {} as little crab pincers that hold a value in place.

The Rust Programming Language · Ch.2 Programming a Guessing Game

13

Chapter 2 · 2.2 Processing a Guess

Placeholders for variables and expressions

  • When printing the value of a variable, the variable name can go inside the curly brackets.
  • When printing the result of evaluating an expression, place empty curly brackets in the format string, then follow the format string with a comma-separated list of expressions to print in each empty curly bracket placeholder, in the same order.

Example 2.6. Printing a variable and the result of an expression in one call to println!:

let x = 5;

let y = 10;

 

println!("x = {x} and y + 2 = {}", y + 2);

This code would print x = 5 and y + 2 = 12.

The Rust Programming Language · Ch.2 Programming a Guessing Game

14

Chapter 2 · 2.2 Processing a Guess

Testing the first part

Let’s test the first part of the guessing game. Run it using cargo run:

$ cargo run

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.44s

     Running `target/debug/guessing_game`

Guess the number!

Please input your guess.

6

You guessed: 6

At this point, the first part of the game is done: we’re getting input from the keyboard and then printing it.

The Rust Programming Language · Ch.2 Programming a Guessing Game

15

Chapter 2 · 2.3 Generating a Secret Number

A secret number from the rand crate

Next we generate a secret number for the user to guess. It should be different every time, so that the game is fun to play more than once, and between 1 and 100, so that the game isn’t too difficult. Rust doesn’t yet include random number functionality in its standard library, but the Rust team provides a rand crate (crates.io/crates/rand) with that functionality.

Increasing functionality with a crate

Remember that a crate is a collection of Rust source code files.

Definition 2.7 (Binary crate). A crate that is an executable, like the project we’ve been building.

Definition 2.8 (Library crate). A crate, like rand, containing code intended to be used in other programs; it can’t be executed on its own.

Coordinating external crates is where Cargo really shines. To use rand, first add it as a dependency: put the line shown next beneath the [dependencies] header in Cargo.toml. Specify rand exactly so, with this version number, or the code examples may not work.

The Rust Programming Language · Ch.2 Programming a Guessing Game

16

Chapter 2 · 2.3 Generating a Secret Number

Declaring the dependency with a SemVer version

Cargo.toml

[dependencies]

rand = "0.10.1"

In Cargo.toml, everything after a header is part of that section until another section starts. [dependencies] tells Cargo which external crates your project depends on and which versions you require — here rand, with the semantic version specifier 0.10.1.

Definition 2.9 (Semantic Versioning). A standard for writing version numbers (semver.org), sometimes called SemVer, which Cargo understands.

  • The specifier 0.10.1 is shorthand for ^0.10.1: any version that is at least 0.10.1 but below 0.11.0.
  • Cargo considers these versions to have public APIs compatible with 0.10.1, so you get the latest patch release that will still compile with the code in this chapter.
  • Any version 0.11.0 or greater is not guaranteed to have the same API as the following examples use.

The Rust Programming Language · Ch.2 Programming a Guessing Game

17

Chapter 2 · 2.3 Generating a Secret Number

Building with the new dependency

Now, without changing any of the code, build the project, as shown in Listing 2.2.

Listing 2.2: The output from running cargo build after adding the rand crate as a dependency

$ cargo build

    Updating crates.io index

     Locking 8 packages to latest Rust 1.96.0 compatible versions

  Downloaded rand_core v0.10.1

  Downloaded chacha20 v0.10.1

  Downloaded rand v0.10.1

  Downloaded 3 crates (162.9KiB) in 0.59s

   Compiling libc v0.2.186

   Compiling rand_core v0.10.1

   Compiling getrandom v0.4.3

   Compiling cfg-if v1.0.4

   Compiling chacha20 v0.10.1

   Compiling rand v0.10.1

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.03s

The Rust Programming Language · Ch.2 Programming a Guessing Game

18

Chapter 2 · 2.3 Generating a Secret Number

What Cargo did: the registry and dependencies

You may see different version numbers (all compatible with the code, thanks to SemVer!) and different lines (depending on the operating system), and the lines may be in a different order.

Definition 2.10 (Registry). A copy of data from Crates.io, from which Cargo fetches the latest versions of everything an external dependency needs. Crates.io is where people in the Rust ecosystem post their open source Rust projects for others to use.

After updating the registry, Cargo checks the [dependencies] section and downloads any listed crates that aren’t already downloaded. Although we only listed rand, Cargo also grabbed the other crates that rand depends on to work. After downloading the crates, Rust compiles them and then compiles the project with the dependencies available.

If you immediately run cargo build again without making any changes, you get no output aside from the Finished line. Cargo knows it has already downloaded and compiled the dependencies, and you haven’t changed anything about them in Cargo.toml. It also knows you haven’t changed your code, so it doesn’t recompile that either. With nothing to do, it simply exits.

The Rust Programming Language · Ch.2 Programming a Guessing Game

19

Chapter 2 · 2.3 Generating a Secret Number

Rebuilding after a change; reproducible builds

If you make a trivial change to src/main.rs, save it and build again, you only see two lines of output:

$ cargo build

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s

Cargo only updates the build with your tiny change to src/main.rs. Your dependencies haven’t changed, so Cargo reuses what it has already downloaded and compiled for those.

Ensuring reproducible builds

Cargo has a mechanism that ensures you can rebuild the same artifact every time you or anyone else builds your code: it uses only the versions of the dependencies you specified until you indicate otherwise. Say that next week version 0.10.2 of rand comes out with an important bug fix, but also a regression that will break your code. To handle this, Rust creates the Cargo.lock file the first time you run cargo build, so we now have this file in the guessing_game directory.

The Rust Programming Language · Ch.2 Programming a Guessing Game

20

Chapter 2 · 2.3 Generating a Secret Number

Cargo.lock and updating a crate

  • On the first build, Cargo figures out all the versions of the dependencies that fit the criteria and writes them to the Cargo.lock file.
  • On later builds, Cargo sees that Cargo.lock exists and uses the versions specified there rather than figuring out versions again. This gives you a reproducible build automatically: the project stays at 0.10.1 until you explicitly upgrade.
  • Because Cargo.lock is important for reproducible builds, it’s often checked into source control with the rest of the code in your project.

Updating a crate to get a new version

When you do want to update a crate, Cargo provides the command update. It ignores the Cargo.lock file, figures out all the latest versions that fit your specifications in Cargo.toml, and writes those versions to Cargo.lock. By default, Cargo only looks for versions greater than 0.10.1 and less than 0.11.0. If the rand crate has released the two new versions 0.10.2 and 0.999.0, running cargo update shows what follows on the next slide.

The Rust Programming Language · Ch.2 Programming a Guessing Game

21

Chapter 2 · 2.3 Generating a Secret Number

Running cargo update

$ cargo update

    Updating crates.io index

     Locking 1 package to latest Rust 1.96.0 compatible version

    Updating rand v0.10.1 -> v0.10.2 (available: v0.999.0)

Cargo ignores the 0.999.0 release. Your Cargo.lock file now notes that the version of rand you are using is 0.10.2. To use rand version 0.999.0 or any version in the 0.999.x series, you’d have to update Cargo.toml to look like this instead (don’t actually make this change: the following examples assume rand 0.10):

[dependencies]

rand = "0.999.0"

The next time you run cargo build, Cargo will update the registry of crates available and reevaluate your rand requirements according to the new version you have specified.

The Rust Programming Language · Ch.2 Programming a Guessing Game

22

Chapter 2 · 2.3 Generating a Secret Number

Generating a random number

There’s a lot more to say about Cargo and its ecosystem (Chapter 14), but that’s all you need for now. Cargo makes it very easy to reuse libraries, so Rustaceans are able to write smaller projects that are assembled from a number of packages.

Let’s start using rand to generate a number to guess. Update src/main.rs as shown in Listing 2.3.

Listing 2.3: Adding code to generate a random number — src/main.rs

use std::io;

 

use rand::prelude::*;

 

fn main() {

    println!("Guess the number!");

 

    let secret_number = rand::rng().random_range(1..=100);

The Rust Programming Language · Ch.2 Programming a Guessing Game

23

Chapter 2 · 2.3 Generating a Secret Number

Bringing rand::prelude into scope

Listing 2.3 (continued): Adding code to generate a random number — src/main.rs

 

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

}

First, we add the line use rand::prelude::*;. The prelude module contains the most commonly used parts of the rand crate, and use makes those items available in our program’s scope.

The Rust Programming Language · Ch.2 Programming a Guessing Game

24

Chapter 2 · 2.3 Generating a Secret Number

rand::rng and random_range

let secret_number = rand::rng().random_range(1..=100);

  • The rand::rng function gives us the particular random number generator we use: one that is local to the current thread of execution and is seeded by the operating system.
  • Then we call the random_range method on the generator. It is defined by the RngExt trait, part of the rand::prelude module that use rand::prelude::*; brought into scope.
  • random_range takes a range expression as an argument and generates a random number in the range. The form start..=end is inclusive on the lower and upper bounds, so 1..=100 requests a number between 1 and 100.

Remark. You won’t just know what to bring into scope and which methods and functions to call from a crate, so each crate has documentation with instructions for using it. Another neat feature of Cargo: cargo doc --open builds the documentation provided by all your dependencies locally and opens it in your browser. For other functionality in the rand crate, for example, run cargo doc --open and click rand in the sidebar on the left.

The Rust Programming Language · Ch.2 Programming a Guessing Game

25

Chapter 2 · 2.3 Generating a Secret Number

Printing the secret number while testing

The second new line prints the secret number. This is useful while we’re developing the program, to be able to test it, but we’ll delete it from the final version. It’s not much of a game if the program prints the answer as soon as it starts!

Try running the program a few times. The first run:

$ cargo run

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s

     Running `target/debug/guessing_game`

Guess the number!

The secret number is: 7

Please input your guess.

4

You guessed: 4

The Rust Programming Language · Ch.2 Programming a Guessing Game

26

Chapter 2 · 2.3 Generating a Secret Number

A different number on every run

The second run, without changes to the code:

$ cargo run

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s

     Running `target/debug/guessing_game`

Guess the number!

The secret number is: 83

Please input your guess.

5

You guessed: 5

  • You should get different random numbers, and they should all be numbers between 1 and 100.
  • If you get warnings, they are safe to ignore.
  • If you get errors, check that you have rand = "0.10.1" in your Cargo.toml: future versions of rand may have a different API, but any version in the 0.10 series should work with the code in this chapter.

The Rust Programming Language · Ch.2 Programming a Guessing Game

27

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

Comparing two numbers with Ordering

Listing 2.4: Handling the possible return values of comparing two numbers — src/main.rs

use std::cmp::Ordering;

use std::io;

 

use rand::prelude::*;

 

fn main() {

    // --snip--

 

    println!("You guessed: {guess}");

 

    match guess.cmp(&secret_number) {

        Ordering::Less => println!("Too small!"),

        Ordering::Greater => println!("Too big!"),

        Ordering::Equal => println!("You win!"),

    }

}

Now that we have user input and a random number, we can compare them, as in Listing 2.4. Note that this code won’t compile just yet, as explained shortly.

First, another use statement brings 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.

The Rust Programming Language · Ch.2 Programming a Guessing Game

28

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

The cmp method and the match expression

Five new lines at the bottom use the Ordering type. The cmp method 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 compares guess to secret_number. It returns a variant of the Ordering enum we brought into scope with the use statement.

A match expression decides 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.

Definition 2.11 (Arm). One branch of a match expression. 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. They are covered in detail in Chapter 6 and Chapter 19, respectively.

The Rust Programming Language · Ch.2 Programming a Guessing Game

29

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

Walking through a match

Example 2.12. Say the user has guessed 50 and the randomly generated secret number this time is 38.

match guess.cmp(&secret_number) {

    Ordering::Less => println!("Too small!"),

    Ordering::Greater => println!("Too big!"),

    Ordering::Equal => println!("You win!"),

}

  1. Comparing 50 to 38, the cmp method returns Ordering::Greater, because 50 is greater than 38.
  2. The match expression gets the Ordering::Greater value and starts checking each arm’s pattern. The first arm’s pattern, Ordering::Less, does not match Ordering::Greater, so it ignores the code in that arm and moves to the next arm.
  3. The next arm’s pattern, Ordering::Greater, does match! The associated code in that arm executes and prints Too big! to the screen.
  4. 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 — the output follows on the next slide.

The Rust Programming Language · Ch.2 Programming a Guessing Game

30

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

Error E0308: mismatched types

$ cargo build

...

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

error[E0308]: mismatched types

  --> src/main.rs:23:21

   |

23 |     match guess.cmp(&secret_number) {

   |                 --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}`

   |                 |

   |                 arguments to this method are incorrect

   |

   = note: expected reference `&String`

              found reference `&{integer}`

note: method defined here

  --> /rustc/2d8144b7880597b6e6d3dfd63a9a9efae3f533d3/library/core/src/cmp.rs:1000:7

 

For more information about this error, try `rustc --explain E0308`.

error: could not compile `guessing_game` (bin "guessing_game") due to 1 previous error

The Rust Programming Language · Ch.2 Programming a Guessing Game

31

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

Why the types don’t match

The core of the error states that there are mismatched types. Rust has a strong, static type system. However, it also has type inference.

Definition 2.13 (Type inference). Rust working out a type you didn’t write. From let mut guess = String::new(), Rust inferred 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.

Table 2.1: A few of Rust’s number types that can hold a value between 1 and 100

TypeDescription
i32a 32-bit number
u32an unsigned 32-bit number
i64a 64-bit number

There are others as well. 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.

The Rust Programming Language · Ch.2 Programming a Guessing Game

32

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

Converting the input to a number

Ultimately, we want to convert the String the program reads as input into a number type, so that we can compare it numerically to the secret number. We do so by adding this line to the main function body:

src/main.rs

    // --snip--

 

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

The Rust Programming Language · Ch.2 Programming a Guessing Game

33

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

The conversion line

src/main.rs (continued)

 

    match guess.cmp(&secret_number) {

        Ordering::Less => println!("Too small!"),

        Ordering::Greater => println!("Too big!"),

        Ordering::Equal => println!("You win!"),

    }

}

The new line is:

let guess: u32 = guess.trim().parse().expect("Please type a number!");

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.

The Rust Programming Language · Ch.2 Programming a Guessing Game

34

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

Shadowing guess and trimming the input

Definition 2.14 (Shadowing). Declaring a new variable with the name of an existing one, so that the new value shadows the previous one. It lets us reuse the guess variable name rather than forcing us to create two unique variables, such as guess_str and guess.

Chapter 3 covers shadowing in more detail; for now, know that it is often used when you want to convert a value from one type to another type.

We bind the 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 eliminates any whitespace at the beginning and end. We must do this before converting the string to a 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. Typing 5 and pressing enter makes guess look like 5\n, where \n represents “newline.” (On Windows, enter results in a carriage return and a newline, \r\n.)
  • trim eliminates \n or \r\n, resulting in just 5.

The Rust Programming Language · Ch.2 Programming a Guessing Game

35

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

parse, the u32 annotation, and failure

The parse method on strings converts a string to another type — here, from a string to a number. We tell Rust the exact number type we want with let guess: u32: the colon (:) after guess tells Rust we’ll annotate the variable’s type.

Rust has a few built-in number types; u32 is an unsigned, 32-bit integer, a good default choice for a small positive number (other number types: Chapter 3).

The u32 annotation and the comparison with secret_number mean Rust infers that secret_number should be a u32 as well. So now the comparison is between two values of the same type!

parse only works on characters that can logically be converted into numbers, so it can easily cause errors: a string such as A👍% cannot be converted to a number. Because it might fail, parse returns a Result, much as read_line does, and we treat it the same way, with expect:

  • Err (no number could be created from the string): expect crashes the game and prints the message we give it.
  • Ok (the conversion succeeded): expect returns the number that we want from the Ok value.

The Rust Programming Language · Ch.2 Programming a Guessing Game

36

Chapter 2 · 2.4 Comparing the Guess to the Secret Number

Running the comparison

Let’s run the program now:

$ cargo run

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.26s

     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.

Most of the game works now, but the user can make only one guess. Let’s change that by adding a loop!

The Rust Programming Language · Ch.2 Programming a Guessing Game

37

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

Adding a loop

The loop keyword creates an infinite loop; add one to src/main.rs to give users more chances:

    // --snip--

 

    println!("The secret number is: {secret_number}");

 

    loop {

        println!("Please input your guess.");

 

        // --snip--

 

        match guess.cmp(&secret_number) {

            Ordering::Less => println!("Too small!"),

            Ordering::Greater => println!("Too big!"),

            Ordering::Equal => println!("You win!"),

        }

    }

}

The Rust Programming Language · Ch.2 Programming a Guessing Game

38

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

A loop with no way out

  • We’ve moved everything from the guess input prompt onward into the loop. Be sure to indent the lines inside the loop another four spaces each, and run the program again.
  • The program now asks for another guess forever, which introduces a new problem: it doesn’t seem like the user can quit!
  • The user could always interrupt the program with the keyboard shortcut ctrl-C.
  • But there’s another way to escape this insatiable monster, as mentioned in the parse discussion in “Comparing the Guess to the Secret Number”: if the user enters a non-number answer, the program crashes. We can take advantage of that to allow the user to quit, as the session on the next two slides shows.

The Rust Programming Language · Ch.2 Programming a Guessing Game

39

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

Guessing in a loop

The game now keeps asking for guesses:

$ cargo run

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.23s

     Running `target/debug/guessing_game`

Guess the number!

The secret number is: 59

Please input your guess.

45

You guessed: 45

Too small!

Please input your guess.

60

You guessed: 60

Too big!

The Rust Programming Language · Ch.2 Programming a Guessing Game

40

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

Quitting by crashing

The session continues — even after a win:

Please input your guess.

59

You guessed: 59

You win!

Please input your guess.

quit

 

thread 'main' (6694925) panicked at src/main.rs:28:47:

Please type a number!: ParseIntError { kind: InvalidDigit }

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Typing quit will quit the game, but as you’ll notice, so will entering any other non-number input. This is suboptimal, to say the least; we want the game to also stop when the correct number is guessed.

The Rust Programming Language · Ch.2 Programming a Guessing Game

41

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

Quitting after a correct guess

Program the game to quit when the user wins by adding a break statement in src/main.rs:

        // --snip--

 

        match guess.cmp(&secret_number) {

            Ordering::Less => println!("Too small!"),

            Ordering::Greater => println!("Too big!"),

            Ordering::Equal => {

                println!("You win!");

                break;

            }

        }

    }

}

Adding the break line after You win! makes the program exit the loop when the user guesses the secret number correctly. Exiting the loop also means exiting the program, because the loop is the last part of main.

The Rust Programming Language · Ch.2 Programming a Guessing Game

42

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

Handling invalid input

Listing 2.5: Ignoring a non-number guess and asking for another guess instead of crashing the program — src/main.rs

        // --snip--

 

        io::stdin()

            .read_line(&mut guess)

            .expect("Failed to read line");

 

        let guess: u32 = match guess.trim().parse() {

            Ok(num) => num,

            Err(_) => continue,

        };

 

        println!("You guessed: {guess}");

 

        // --snip--

Rather than crashing the program when the user inputs a non-number, let’s make the game ignore a non-number so that the user can continue guessing. We do that by altering the line where guess is converted from a String to a u32.

We switch from an expect call to a match expression, moving from crashing on an error to handling the error. parse returns a Result, an enum with the variants Ok and Err; we match on it just as we did with the Ordering result of cmp.

The Rust Programming Language · Ch.2 Programming a Guessing Game

43

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

How the match handles Ok and Err

If parse is able to turn the string into a number, it returns an Ok value that contains the resultant number. That Ok value matches the first arm’s pattern, and the match expression just returns the num value that parse produced and put inside the Ok value. That number ends up right where we want it, in the new guess variable we’re creating.

If parse is not able to turn the string into a number, it returns an Err value that contains more information about the error. The Err value does not match the Ok(num) pattern in the first arm, but it does match the Err(_) pattern in the second arm.

Definition 2.15 (Catch-all value). The underscore, _, in a pattern. Err(_) says we want to match all Err values, no matter what information they have inside them.

So the program executes the second arm’s code, continue, which tells the program to go to the next iteration of the loop and ask for another guess. Effectively, the program ignores all errors that parse might encounter!

The Rust Programming Language · Ch.2 Programming a Guessing Game

44

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

Trying the finished logic

Now everything in the program should work as expected. Let’s try it:

$ cargo run

   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s

     Running `target/debug/guessing_game`

Guess the number!

The secret number is: 61

Please input your guess.

10

You guessed: 10

Too small!

Please input your guess.

99

You guessed: 99

Too big!

The Rust Programming Language · Ch.2 Programming a Guessing Game

45

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

Ignoring invalid input, stopping on a win

The session continues: foo is ignored, and the correct guess ends the game.

Please input your guess.

foo

Please input your guess.

61

You guessed: 61

You win!

Awesome! With one tiny final tweak, we will finish the guessing game. Recall that the program is still printing the secret number. That worked well for testing, but it ruins the game. Let’s delete the println! that outputs the secret number. Listing 2.6 shows the final code.

The Rust Programming Language · Ch.2 Programming a Guessing Game

46

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

The complete guessing game (1/3)

Listing 2.6: Complete guessing game code — src/main.rs

use std::cmp::Ordering;

use std::io;

 

use rand::prelude::*;

 

fn main() {

    println!("Guess the number!");

 

    let secret_number = rand::rng().random_range(1..=100);

 

    loop {

        println!("Please input your guess.");

 

        let mut guess = String::new();

 

The Rust Programming Language · Ch.2 Programming a Guessing Game

47

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

The complete guessing game (2/3)

Listing 2.6 (continued): Complete guessing game code — src/main.rs

        io::stdin()

            .read_line(&mut guess)

            .expect("Failed to read line");

 

        let guess: u32 = match guess.trim().parse() {

            Ok(num) => num,

            Err(_) => continue,

        };

 

        println!("You guessed: {guess}");

 

The Rust Programming Language · Ch.2 Programming a Guessing Game

48

Chapter 2 · 2.5 Allowing Multiple Guesses with Looping

The complete guessing game (3/3)

Listing 2.6 (continued): Complete guessing game code — src/main.rs

        match guess.cmp(&secret_number) {

            Ordering::Less => println!("Too small!"),

            Ordering::Greater => println!("Too big!"),

            Ordering::Equal => {

                println!("You win!");

                break;

            }

        }

    }

}

At this point, you’ve successfully built the guessing game. Congratulations!

The Rust Programming Language · Ch.2 Programming a Guessing Game

49

Chapter 2 · Summary

Chapter summary

This project was a hands-on way to introduce many new Rust concepts: let, match, functions, the use of external crates, and more. Along the way the guessing game used:

  • variables, immutable by default, made mutable with mut, and shadowing to convert a value’s type;
  • associated functions (String::new), methods, and references (&mut guess);
  • Result with Ok/Err, handled by expect or by a match;
  • the rand crate as a dependency, SemVer specifiers, Cargo.lock and cargo update;
  • Ordering and cmp, loop, break and continue.

Remark. The next few chapters cover these concepts in more detail. Chapter 3 covers concepts that most programming languages have, such as variables, data types, and functions, and shows how to use them in Rust. Chapter 4 explores ownership, a feature that makes Rust different from other languages. Chapter 5 discusses structs and method syntax, and Chapter 6 explains how enums work.

The Rust Programming Language · Ch.2 Programming a Guessing Game

50