Guessing Game

Okay! We've got the basics of Rust down. Let's write a bigger program.

For our first project, we'll implement a classic beginner programming problem: the guessing game. Here's how it works: Our program will generate a random integer between one and a hundred. It will then prompt us to enter a guess. Upon entering our guess, it will tell us if we're too low or too high. Once we guess correctly, it will congratulate us. Sound good?

Set up

Let's set up a new project. Go to your projects directory. Remember how we had to create our directory structure and a Cargo.toml for hello_world? Cargo has a command that does that for us. Let's give it a shot:

$ cd ~/projects
$ cargo new guessing_game --bin
$ cd guessing_game

We pass the name of our project to cargo new, and then the --bin flag, since we're making a binary, rather than a library.

Check out the generated Cargo.toml:

[package]

name = "guessing_game"
version = "0.0.1"
authors = ["Your Name <you@example.com>"]

Cargo gets this information from your environment. If it's not correct, go ahead and fix that.

Finally, Cargo generated a "Hello, world!" for us. Check out src/main.rs:

fn main() {
    println!("Hello, world!")
}

Let's try compiling what Cargo gave us:

$ cargo build
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)

Excellent! Open up your src/main.rs again. We'll be writing all of our code in this file. We'll talk about multiple-file projects later on in the guide.

Before we move on, let me show you one more Cargo command: run. cargo run is kind of like cargo build, but it also then runs the produced executable. Try it out:

$ cargo run
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
     Running `target/guessing_game`
Hello, world!

Great! The run command comes in handy when you need to rapidly iterate on a project. Our game is just such a project, we need to quickly test each iteration before moving on to the next one.

Processing a Guess

Let's get to it! The first thing we need to do for our guessing game is allow our player to input a guess. Put this in your src/main.rs:

use std::old_io;

fn main() {
    println!("Guess the number!");

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");

    println!("You guessed: {}", input);
}

You've seen this code before, when we talked about standard input. We import the std::io module with use, and then our main function contains our program's logic. We print a little message announcing the game, ask the user to input a guess, get their input, and then print it out.

Because we talked about this in the section on standard I/O, I won't go into more details here. If you need a refresher, go re-read that section.

Generating a secret number

Next, we need to generate a secret number. To do that, we need to use Rust's random number generation, which we haven't talked about yet. Rust includes a bunch of interesting functions in its standard library. If you need a bit of code, it's possible that it's already been written for you! In this case, we do know that Rust has random number generation, but we don't know how to use it.

Enter the docs. Rust has a page specifically to document the standard library. You can find that page here. There's a lot of information on that page, but the best part is the search bar. Right up at the top, there's a box that you can enter in a search term. The search is pretty primitive right now, but is getting better all the time. If you type "random" in that box, the page will update to this one. The very first result is a link to std::rand::random. If we click on that result, we'll be taken to its documentation page.

This page shows us a few things: the type signature of the function, some explanatory text, and then an example. Let's try to modify our code to add in the random function and see what happens:

use std::old_io;
use std::rand;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random() % 100) + 1; // secret_number: i32

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

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");


    println!("You guessed: {}", input);
}

The first thing we changed was to use std::rand, as the docs explained. We then added in a let expression to create a variable binding named secret_number, and we printed out its result.

Also, you may wonder why we are using % on the result of rand::random(). This operator is called modulo, and it returns the remainder of a division. By taking the modulo of the result of rand::random(), we're limiting the values to be between 0 and 99. Then, we add one to the result, making it from 1 to 100. Using modulo can give you a very, very small bias in the result, but for this example, it is not important.

Let's try to compile this using cargo build:

$ cargo build
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
src/main.rs:7:26: 7:34 error: the type of this value must be known in this context
src/main.rs:7     let secret_number = (rand::random() % 100) + 1;
                                       ^~~~~~~~
error: aborting due to previous error

It didn't work! Rust says "the type of this value must be known in this context." What's up with that? Well, as it turns out, rand::random() can generate many kinds of random values, not just integers. And in this case, Rust isn't sure what kind of value random() should generate. So we have to help it. With number literals, we can just add an i32 onto the end to tell Rust they're integers, but that does not work with functions. There's a different syntax, and it looks like this:

rand::random::<i32>();

This says "please give me a random i32 value." We can change our code to use this hint:

use std::old_io;
use std::rand;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<i32>() % 100) + 1;

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

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");


    println!("You guessed: {}", input);
}

Try running our new program a few times:

$ cargo run
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
     Running `target/guessing_game`
Guess the number!
The secret number is: 7
Please input your guess.
4
You guessed: 4
$ ./target/guessing_game
Guess the number!
The secret number is: 83
Please input your guess.
5
You guessed: 5
$ ./target/guessing_game
Guess the number!
The secret number is: -29
Please input your guess.
42
You guessed: 42

Wait. Negative 29? We wanted a number between one and a hundred! We have two options here: we can either ask random() to generate an unsigned integer, which can only be positive, or we can use the abs() function. Let's go with the unsigned integer approach. If we want a random positive number, we should ask for a random positive number. Our code looks like this now:

use std::old_io;
use std::rand;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");


    println!("You guessed: {}", input);
}

And trying it out:

$ cargo run
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
     Running `target/guessing_game`
Guess the number!
The secret number is: 57
Please input your guess.
3
You guessed: 3

Great! Next up: let's compare our guess to the secret guess.

Comparing guesses

If you remember, earlier in the guide, we made a cmp function that compared two numbers. Let's add that in, along with a match statement to compare our guess to the secret number:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");


    println!("You guessed: {}", input);

    match cmp(input, secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

fn cmp(a: i32, b: i32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

If we try to compile, we'll get some errors:

$ cargo build
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
src/main.rs:20:15: 20:20 error: mismatched types: expected `i32` but found `collections::string::String` (expected i32 but found struct collections::string::String)
src/main.rs:20     match cmp(input, secret_number) {
                             ^~~~~
src/main.rs:20:22: 20:35 error: mismatched types: expected `i32` but found `u32` (expected i32 but found u32)
src/main.rs:20     match cmp(input, secret_number) {
                                    ^~~~~~~~~~~~~
error: aborting due to 2 previous errors

This often happens when writing Rust programs, and is one of Rust's greatest strengths. You try out some code, see if it compiles, and Rust tells you that you've done something wrong. In this case, our cmp function works on integers, but we've given it unsigned integers. In this case, the fix is easy, because we wrote the cmp function! Let's change it to take u32s:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");


    println!("You guessed: {}", input);

    match cmp(input, secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

fn cmp(a: u32, b: u32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

And try compiling again:

$ cargo build
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
src/main.rs:20:15: 20:20 error: mismatched types: expected `u32` but found `collections::string::String` (expected u32 but found struct collections::string::String)
src/main.rs:20     match cmp(input, secret_number) {
                             ^~~~~
error: aborting due to previous error

This error is similar to the last one: we expected to get a u32, but we got a String instead! That's because our input variable is coming from the standard input, and you can guess anything. Try it:

$ ./target/guessing_game
Guess the number!
The secret number is: 73
Please input your guess.
hello
You guessed: hello

Oops! Also, you'll note that we just ran our program even though it didn't compile. This works because the older version we did successfully compile was still lying around. Gotta be careful!

Anyway, we have a String, but we need a u32. What to do? Well, there's a function for that:

let input = old_io::stdin().read_line()
                       .ok()
                       .expect("Failed to read line");
let input_num: Result<u32, _> = input.parse();

The parse function takes in a &str value and converts it into something. We tell it what kind of something with a type hint. Remember our type hint with random()? It looked like this:

rand::random::<u32>();

There's an alternate way of providing a hint too, and that's declaring the type in a let:

let x: u32 = rand::random();

In this case, we say x is a u32 explicitly, so Rust is able to properly tell random() what to generate. In a similar fashion, both of these work:

let input_num = "5".parse::<u32>(); // input_num: Option<u32>
let input_num: Result<u32, _> = "5".parse(); // input_num: Result<u32, <u32 as FromStr>::Err>

Here we're converting the Result returned by parse to an Option by using the ok method as well. Anyway, with us now converting our input to a number, our code looks like this:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");
    let input_num: Result<u32, _> = input.parse();

    println!("You guessed: {:?}", input_num);

    match cmp(input_num, secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

fn cmp(a: u32, b: u32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

Let's try it out!

$ cargo build
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
src/main.rs:22:15: 22:24 error: mismatched types: expected `u32` but found `core::option::Option<u32>` (expected u32 but found enum core::option::Option)
src/main.rs:22     match cmp(input_num, secret_number) {
                             ^~~~~~~~~
error: aborting due to previous error

Oh yeah! Our input_num has the type Option<u32>, rather than u32. We need to unwrap the Option. If you remember from before, match is a great way to do that. Try this code:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");
    let input_num: Result<u32, _> = input.parse();

    let num = match input_num {
        Ok(num) => num,
        Err(_) => {
            println!("Please input a number!");
            return;
        }
    };


    println!("You guessed: {}", num);

    match cmp(num, secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

fn cmp(a: u32, b: u32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

We use a match to either give us the u32 inside of the Option, or else print an error message and return. Let's give this a shot:

$ cargo run
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
     Running `target/guessing_game`
Guess the number!
The secret number is: 17
Please input your guess.
5
Please input a number!

Uh, what? But we did!

... actually, we didn't. See, when you get a line of input from stdin(), you get all the input. Including the \n character from you pressing Enter. Therefore, parse() sees the string "5\n" and says "nope, that's not a number; there's non-number stuff in there!" Luckily for us, &strs have an easy method we can use defined on them: trim(). One small modification, and our code looks like this:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

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

    let input = old_io::stdin().read_line()
                           .ok()
                           .expect("Failed to read line");
    let input_num: Result<u32, _> = input.trim().parse();

    let num = match input_num {
        Ok(num) => num,
        Err(_) => {
            println!("Please input a number!");
            return;
        }
    };


    println!("You guessed: {}", num);

    match cmp(num, secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

fn cmp(a: u32, b: u32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

Let's try it!

$ cargo run
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
     Running `target/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
  76
You guessed: 76
Too big!

Nice! You can see I even added spaces before my guess, and it still figured out that I guessed 76. Run the program a few times, and verify that guessing the number works, as well as guessing a number too small.

The Rust compiler helped us out quite a bit there! This technique is called "leaning on the compiler", and it's often useful when working on some code. Let the error messages help guide you towards the correct types.

Now we've got most of the game working, but we can only make one guess. Let's change that by adding loops!

Looping

As we already discussed, the loop keyword gives us an infinite loop. Let's add that in:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

    loop {

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

        let input = old_io::stdin().read_line()
                               .ok()
                               .expect("Failed to read line");
        let input_num: Result<u32, _> = input.trim().parse();

        let num = match input_num {
            Ok(num) => num,
            Err(_) => {
                println!("Please input a number!");
                return;
            }
        };


        println!("You guessed: {}", num);

        match cmp(num, secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => println!("You win!"),
        }
    }
}

fn cmp(a: u32, b: u32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

And try it out. But wait, didn't we just add an infinite loop? Yup. Remember that return? If we give a non-number answer, we'll return and quit. Observe:

$ cargo run
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
     Running `target/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!
Please input your guess.
59
You guessed: 59
You win!
Please input your guess.
quit
Please input a number!

Ha! quit actually quits. As does any other non-number input. Well, this is suboptimal to say the least. First, let's actually quit when you win the game:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

    loop {

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

        let input = old_io::stdin().read_line()
                               .ok()
                               .expect("Failed to read line");
        let input_num: Result<u32, _> = input.trim().parse();

        let num = match input_num {
            Ok(num) => num,
            Err(_) => {
                println!("Please input a number!");
                return;
            }
        };


        println!("You guessed: {}", num);

        match cmp(num, secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                return;
            },
        }
    }
}

fn cmp(a: u32, b: u32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

By adding the return line after the You win!, we'll exit the program when we win. We have just one more tweak to make: when someone inputs a non-number, we don't want to quit, we just want to ignore it. Change that return to continue:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

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

    loop {

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

        let input = old_io::stdin().read_line()
                               .ok()
                               .expect("Failed to read line");
        let input_num: Result<u32, _> = input.trim().parse();

        let num = match input_num {
            Ok(num) => num,
            Err(_) => {
                println!("Please input a number!");
                continue;
            }
        };


        println!("You guessed: {}", num);

        match cmp(num, secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                return;
            },
        }
    }
}

fn cmp(a: u32, b: u32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

Now we should be good! Let's try:

$ cargo run
   Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
     Running `target/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!
Please input your guess.
foo
Please input a number!
Please input your guess.
61
You guessed: 61
You win!

Awesome! With one tiny last tweak, we have finished the guessing game. Can you think of what it is? That's right, we don't want to print out the secret number. It was good for testing, but it kind of ruins the game. Here's our final source:

use std::old_io;
use std::rand;
use std::cmp::Ordering;

fn main() {
    println!("Guess the number!");

    let secret_number = (rand::random::<u32>() % 100) + 1;

    loop {

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

        let input = old_io::stdin().read_line()
                               .ok()
                               .expect("Failed to read line");
        let input_num: Result<u32, _> = input.trim().parse();

        let num = match input_num {
            Ok(num) => num,
            Err(_) => {
                println!("Please input a number!");
                continue;
            }
        };


        println!("You guessed: {}", num);

        match cmp(num, secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                return;
            },
        }
    }
}

fn cmp(a: u32, b: u32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

Complete!

At this point, you have successfully built the Guessing Game! Congratulations!

You've now learned the basic syntax of Rust. All of this is relatively close to various other programming languages you have used in the past. These fundamental syntactical and semantic elements will form the foundation for the rest of your Rust education.

Now that you're an expert at the basics, it's time to learn about some of Rust's more unique features.