rust-book-cn/nostarch/chapter02.md

1021 lines
36 KiB
Markdown
Raw Normal View History

2016-08-20 05:13:41 +08:00
[TOC]
2016-08-03 10:07:25 +08:00
# Guessing Game
2016-08-20 05:13:41 +08:00
Let's jump into Rust with a hands-on project! This chapter will introduce you to
a few common Rust concepts by showing how you would use them in a real program.
You'll learn about `let`, `match`, methods, associated functions, using
external crates, and more! Following chapters will explore these ideas in more
detail.
Were going to implement a classic beginner programming problem: the guessing
game. Heres 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 were too low or too high. Once we guess correctly,
it will congratulate us.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
## Setting Up a New Project
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
Lets set up a new project. Go to your projects directory from the previous
chapter, and create a new project using Cargo, like so:
2016-08-03 10:07:25 +08:00
```bash
$ cargo new guessing_game --bin
$ cd guessing_game
```
2016-08-20 05:13:41 +08:00
We pass the name of our project to `cargo new` and pass the `--bin` flag, since
2016-08-03 10:07:25 +08:00
were going to be making another binary like in Chapter 1.
Take a look at the generated `Cargo.toml`:
2016-08-20 05:13:41 +08:00
Filename: Cargo.toml
2016-08-03 10:07:25 +08:00
```toml
[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
[dependencies]
```
2016-08-20 05:13:41 +08:00
If the author information that Cargo got from your environment is not correct,
go ahead and fix that in the file and save it again.
And as we saw in the last chapter, `cargo new` generates a "Hello, world!"
program for us. Check out `src/main.rs`:
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
Filename: src/main.rs
2016-08-03 10:07:25 +08:00
```rust
fn main() {
println!("Hello, world!");
}
```
2016-08-20 05:13:41 +08:00
Lets try compiling what Cargo gave us and running it in the same step, using
the `cargo run` command:
2016-08-03 10:07:25 +08:00
```bash
$ cargo run
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
Running `target/debug/guessing_game`
Hello, world!
```
Great! The `run` command comes in handy when you need to rapidly iterate on a
project. Our game is such a project: we want to quickly test each
iteration before moving on to the next one.
Now open up your `src/main.rs` again. Well be writing all of our code in this
file.
## Processing a Guess
2016-08-20 05:13:41 +08:00
Lets get to it! We'll split the development of this game up into parts. This
first part will ask for input from a user and process the input, checking that
the input is in the form we expect. First we need to allow our player to input
a guess. Enter this in your `src/main.rs`:
Filename: src/main.rs
2016-08-03 10:07:25 +08:00
```rust,ignore
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);
}
```
Theres a lot here! Lets go over it, bit by bit.
```rust,ignore
use std::io;
```
2016-08-20 05:13:41 +08:00
Well need to take user input and then print the result as output, and for that
functonality we need to import the `io` (input/output) library from the
standard library (which is known as `std`).
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
By default, Rust only imports a few things into every program in the
*prelude*. If its not in the prelude, youll have to import it into
your program explicitly with a `use` statement. Using the `std::io` library
gets you a number of useful `io`-related things, including the functionality to
accept user input.
2016-08-03 10:07:25 +08:00
```rust,ignore
fn main() {
```
As youve seen in Chapter 1, the `main()` function is the entry point into the
program. The `fn` syntax declares a new function, the `()`s indicate that
there are no arguments, and `{` starts the body of the function.
```rust,ignore
println!("Guess the number!");
println!("Please input your guess.");
```
2016-08-20 05:13:41 +08:00
As we learned in Chapter 1, `println!()` is a macro that prints a string to the
screen. This is just a prompt stating what the game is and requesting input from
the user.
### Storing Values with Variable Bindings
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
Next we need to store the user input.
2016-08-03 10:07:25 +08:00
```rust,ignore
let mut guess = String::new();
```
Now were getting interesting! Theres a lot going on in this little line.
2016-08-20 05:13:41 +08:00
The first thing to notice is that this is a `let` statement, which is
used to create *variable bindings*. Here's another example:
2016-08-03 10:07:25 +08:00
```rust,ignore
let foo = bar;
```
This will create a new binding named `foo`, and bind it to the value `bar`. In
2016-08-20 05:13:41 +08:00
many languages, this is called a *variable*, but Rusts variable bindings have
a few differences.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
For example, theyre immutable by default. To make our binding mutable, our
example uses `mut` before the binding name.
2016-08-03 10:07:25 +08:00
```rust
let foo = 5; // immutable.
let mut bar = 5; // mutable
```
2016-08-20 05:13:41 +08:00
> Note: The `//` syntax will start a comment that continues until the end of the
> line. Rust ignores everything in comments.
2016-08-03 10:07:25 +08:00
So now we know that `let mut guess` will introduce a mutable binding named
2016-08-20 05:13:41 +08:00
`guess`, but we have to look at the other side of the `=` for the value its
2016-08-03 10:07:25 +08:00
bound to: `String::new()`.
`String` is a string type, provided by the standard library. A
2016-08-20 05:13:41 +08:00
`String` is a growable, UTF-8 encoded bit of text.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
The `::` syntax in the `::new()` line indicates that `new()` is an *associated
function* of a particular type. An associated function is a function that is
associated with a type, in this case `String`, rather than a particular
instance of a `String`. Some languages call this a *static method*.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
This `new()` function creates a new, empty `String`.
2016-08-03 10:07:25 +08:00
Youll find a `new()` function on many types, as its a common name for making
a new value of some kind.
2016-08-20 05:13:41 +08:00
So to summarize, the `let mut guess = String::new();` line has created a
mutable binding that is currently bound to a new, empty instance of a `String`.
Whew!
2016-08-03 10:07:25 +08:00
Lets move forward:
```rust,ignore
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
```
2016-08-20 05:13:41 +08:00
Remember how we said `use std::io;` on the first line of the program? Were now
2016-08-03 10:07:25 +08:00
calling an associated function on it. If we didnt `use std::io`, we could
have written this line as `std::io::stdin()`.
2016-08-20 05:13:41 +08:00
This particular function returns an instance of `std::io::Stdin`,
which is a type that represents a handle to the standard input for your
terminal.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
The next part, `.read_line(&mut guess)`, calls the `readline()` method on the standard input handle to get input from the user. Were also
2016-08-03 10:07:25 +08:00
passing one argument to `read_line()`: `&mut guess`.
2016-08-20 05:13:41 +08:00
The job of `read_line()` is to take whatever the user types into standard input
and place that into a string, so it takes that string as an argument. The
string argument needs to be mutable so that the method can change the string's
content by adding the user input.
The `&` indicates that this argument is a *reference*, which gives you a way to
allow multiple parts of your code to access to one piece of data without
needing to copy that data into memory multiple times. References are a complex
feature, and one of Rusts major advantages is how safe and easy it is to use
references. We dont need to know a lot of those details to finish our program
right now, though; Chapter XX will cover references in more detail. For now,
all we need to know is that like `let` bindings, references are immutable by
default. Hence, we need to write `&mut guess`, rather than `&guess` to make it
mutable.
Were not quite done with this line of code. While its a single line of text,
its only the first part of the single logical line of code. The second part is
this method:
2016-08-03 10:07:25 +08:00
```rust,ignore
.expect("Failed to read line");
```
2016-08-20 05:13:41 +08:00
When you call a method with the `.foo()` syntax, it's often wise to introduce a
newline and other whitespace. This helps you split up long lines. We _could_
have written this code as:
2016-08-03 10:07:25 +08:00
```rust,ignore
io::stdin().read_line(&mut guess).expect("failed to read line");
```
But that gets hard to read. So weve split it up, two lines for two method
2016-08-20 05:13:41 +08:00
calls. Now let's see what this line does.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
### Handling Potential Failure with the `Result` Type
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
We mentioned that `read_line()` puts what the user types into the string we
pass it, but it also returns a value: in this case, an `io::Result`. Rust has a
number of types named `Result` in its standard library: a generic `Result`, and
then specific versions for sub-libraries, like `io::Result`.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
The `Result` types are enums, which is short for *enumeration*. An
enumeration is a type that can have a fixed set of values, which are called the
`enum`'s *variants*. We will be covering enums in more detail in Chapter XX.
For `Result`, the variants are `Ok` or `Err`. `Ok` means the operation was
successful, and inside the `Ok` variant is the successfully generated value.
`Err` means the operation failed, and the `Err` contains information about how
or why the operation failed.
2016-08-03 10:07:25 +08:00
The purpose of these `Result` types is to encode error handling information.
Values of the `Result` type, like any type, have methods defined on them. In
2016-08-20 05:13:41 +08:00
this case, `io::Result` has an `expect()` method that we can call. If
this instance of `io::Result` is an `Err` value, `expect()` will cause our
program to crash and display the message that we passed as an argument to
`expect()`. In this case, if the `read_line()` method returns an `Err`, it would
likely be the result of an error coming from the underlying operating system.
If this instance of `io::Result` is an `Ok` value, `expect()` will take the
return value that `Ok` is holding out of the `Ok` and return just that value to
us so that we can use it. In this case, that value will be what the user
entered into standard input.
If we don't call `expect()`, our program will compile, but well get a warning:
2016-08-03 10:07:25 +08:00
```bash
$ cargo build
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
src/main.rs:10:5: 10:39 warning: unused result which must be used,
#[warn(unused_must_use)] on by default
src/main.rs:10 io::stdin().read_line(&mut guess);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
2016-08-20 05:13:41 +08:00
Rust warns that we havent used the `Result` value, telling us that we
havent handled a possible error. The right way to suppress the warning is to
actually write error handling, but if we just want to crash the program when a
problem occurs, we can use `expect()`. Well save recovering from errors for a
future project.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
### Printing Values with `println!()` Placeholders
2016-08-03 10:07:25 +08:00
Theres only one line of this first example left, aside from the closing curly
brace:
```rust,ignore
println!("You guessed: {}", guess);
```
This prints out the string we saved our input in. The `{}`s are a placeholder:
2016-08-20 05:13:41 +08:00
think of `{}` as little crab pincers, holding a value in place. You can print
more than one value this way: the first `{}` holds the first value listed after
the format string, the second set holds the second value, and so on. Printing
out multiple values in one call to `println!()` would then look like this:
2016-08-03 10:07:25 +08:00
```rust
let x = 5;
let y = 10;
2016-08-20 05:13:41 +08:00
println!("x = {} and y = {}", x, y);
2016-08-03 10:07:25 +08:00
```
2016-08-20 05:13:41 +08:00
Which would print out "x = 5 and y = 10".
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
### Testing the First Part
Back to our guessing game, let's test what we have so far. We can run it with
`cargo run`:
2016-08-03 10:07:25 +08:00
```bash
$ cargo run
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6
```
All right! Our first part is done: we can get input from the keyboard and then
print it back out.
2016-08-20 05:13:41 +08:00
## Generating a Secret Number
Next, we need to generate a secret number that the user is trying to guess. The
secret number should be different every time so that the game is fun to play
more than once. So we'd like to have a random number between 1 and 100. Rust
does not yet include random number functionality in its standard library. The
Rust team does, however, provide a `rand` crate at *https://crates.io/crates/rand*.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
### Using a Crate to Get More Functionality
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
Remember that *crate* is what we call a package of Rust code. The project weve
been building is a *binary crate*, which is an executable. The `rand` crate is
a *library crate*, which contains code intended to be used in other programs.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
Cargo's use of external crates is where it really shines. Before we can write
the code using `rand`, we need to modify our `Cargo.toml` to include the `rand`
crate as a dependency. Open it up, and add this line at the bottom beneath the
`[dependencies]` section header that Cargo created for you:
Filename: Cargo.toml
2016-08-03 10:07:25 +08:00
```toml
[dependencies]
rand = "0.3.14"
```
2016-08-20 05:13:41 +08:00
In the `Cargo.toml` file, everything that follows a header is part of a section
that goes until another section starts. Cargo uses the `[dependencies]` section
to know what external crates your project depends on and what versions of those
crates you require. In this case, weve specified the `rand` crate with the
semantic version specifier `0.3.14`. Cargo understands Semantic
Versioning (sometimes called *semver*), which is a standard for
writing version numbers. A bare number like above is actually shorthand for
`^0.3.14`, which means "any version that has a public API compatible with
version 0.3.14".
2016-08-03 10:07:25 +08:00
Now, without changing any of our code, lets build our project:
```bash
$ cargo build
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading rand v0.3.14
Downloading libc v0.2.14
Compiling libc v0.2.14
Compiling rand v0.3.14
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
```
2016-08-20 05:13:41 +08:00
You may see different version numbers (but they will all be compatible with
your code, thanks to semver!) and the lines may be in a different order.
2016-08-03 10:07:25 +08:00
Lots of new output! Now that we have an external dependency, Cargo fetches the
latest versions of everything from the *registry*, which is a copy of data from
2016-08-20 05:13:41 +08:00
*https://crates.io*. Crates.io is where people in the Rust ecosystem
2016-08-03 10:07:25 +08:00
post their open source Rust projects for others to use.
After updating the registry, Cargo checks our `[dependencies]` and downloads
2016-08-20 05:13:41 +08:00
any we dont have yet. In this case, while we only listed `rand` as a
dependency, weve also grabbed a copy of `libc`, because `rand` depends on
`libc` to work. After downloading them, Rust compiles them and then compiles
2016-08-03 10:07:25 +08:00
our project.
If we run `cargo build` again, well get different output:
```bash
$ cargo build
```
Thats right, no output! Cargo knows that our project has been built, that
all of its dependencies are built, and that no changes have been made. Theres
no reason to do all that stuff again. With nothing to do, it simply
exits. If we open up `src/main.rs`, make a trivial change, then save it again,
well only see one line:
```bash
$ cargo build
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
```
2016-08-20 05:13:41 +08:00
This just updates the build with your tiny change to the `main.rs` file.
#### The Cargo.lock File that Ensures Reproducible Builds
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
Cargo has a mechanism to make sure we can rebuild the exact same artifact every
time we or anyone else builds our code: Cargo will only use the versions of the
dependencies you specified until you say otherwise. For example, what happens
if next week version `v0.3.15` of the `rand` crate comes out, containing an
important bugfix, but that also contains a regression that will break our code?
2016-08-03 10:07:25 +08:00
The answer to this problem is the `Cargo.lock` file created the first time we
ran `cargo build` that is now in your project directory. When you build your
2016-08-20 05:13:41 +08:00
project for the first time, Cargo figures out all of the versions of your
dependencies that fit your criteria then writes them to the `Cargo.lock` file.
When you build your project in the future, Cargo will see that the `Cargo.lock`
file exists and use the versions specified there rather than doing all the work
of figuring out versions again. This lets you have a reproducible build
automatically. In other words, our project will stay at `0.3.14` until we
explicitly upgrade, thanks to the lock file.
#### Updating a Crate to Get a New Version
When we _do_ want to update a crate, Cargo has another command,
`update`, which will:
- Ignore the `Cargo.lock` file and figure out all the latest versions that fit
our specifications in `Cargo.toml`.
- If that works, write those versions out to the lock file.
But by default, Cargo will only look for versions larger than `0.3.0` and
smaller than `0.4.0`. If the `rand` crate has released two new versions,
`0.3.15` and `0.4.0`, this is what we would see if we ran `cargo update`:
```bash
$ cargo update
Updating registry `https://github.com/rust-lang/crates.io-index`
Updating rand v0.3.14 -> v0.3.15
```
At this point, you would also notice a change in your `Cargo.lock` file noting
that the version of the `rand` crate you are now using is `0.3.15`.
If we wanted to use `rand` version `0.4.0` or any version in the `0.4.x`
series, wed have to update what is in the `Cargo.toml` file to look like this
instead:
```toml
[dependencies]
rand = "0.4.0"
```
The next time we `cargo build`, assuming that the `rand` crate version `0.4.0`
has been released, Cargo will update the crates index and re-evaluate our
`rand` requirements according to the new version we have specified.
Theres a lot more to say about Cargo and its ecosystem that we will get into
in Chapter XX, but for now, thats all we need to know. Cargo makes it really
easy to re-use libraries, so Rustaceans are able to write smaller projects
which are assembled out of a number of sub-packages.
### Generating a Random Number
Lets get on to actually _using_ `rand`. Our next step is to update our
`main.rs` code as follows:
Filename: src/main.rs
2016-08-03 10:07:25 +08:00
```rust,ignore
extern crate rand;
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);
}
```
2016-08-20 05:13:41 +08:00
First we've added a line to the top, `extern crate rand`, that lets Rust know
well be making use of that external dependency. This also does the equivalent
of calling `use rand`, so we can now call anything in the `rand` crate by
prefixing it with `rand::`.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
Next, we added another `use` line: `use rand::Rng`. `Rng` is a trait that
defines methods that random number generators implement, and this trait must be
in scope for us to use those methods. We'll cover traits in detail in the
Traits section in Chapter XX.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
We also added two more lines in the middle:
2016-08-03 10:07:25 +08:00
```rust,ignore
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is: {}", secret_number);
```
2016-08-20 05:13:41 +08:00
`rand::thread_rng()` is a function that will give us the particular random
number generator that we're going to use: one that is local to our current
thread of execution and seeded by the operating system. Next, we call the
`gen_range()` method on our random number generator. This method is one that is
defined by the Rng trait that we brought into scope with the `use rand::Rng`
statement above. `gen_range()` takes two numbers as arguments and generates a
random number between them. Its inclusive on the lower bound but exclusive on
the upper bound, so we need `1` and `101` to ask for a number ranging from one
to a hundred.
Knowing what traits to import and what functions and methods to use from a
crate isn't something that you'll just *know*. Instructions for using a crate
are in each crate's documentation. Another neat feature of Cargo is that you
can run the `cargo doc --open` command to build documentation provided by all
of your dependencies locally and then open it in your browser. If you're
interested in other functionality in the `rand` crate, for example, run `cargo
doc --open` then click on "rand" in the sidebar on the left.
The second line that we added to our code prints out the secret number. This is
useful while were developing our program to let us easily test it out, but
well be deleting it for the final version. Its not much of a game if it
prints out the answer when you start it up!
2016-08-03 10:07:25 +08:00
Try running our new program a few times:
```bash
$ cargo run
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 7
Please input your guess.
4
You guessed: 4
$ cargo run
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 83
Please input your guess.
5
You guessed: 5
```
2016-08-20 05:13:41 +08:00
You should get different random numbers, and they should all be numbers between
1 and 100. Great job!
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
## Comparing Our Guesses
2016-08-03 10:07:25 +08:00
Now that weve got user input, lets compare our guess to the secret number.
2016-08-20 05:13:41 +08:00
Heres that part of our next step:
Filename: src/main.rs
2016-08-03 10:07:25 +08:00
```rust,ignore
extern crate rand;
use std::io;
use std::cmp::Ordering;
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!"),
}
}
```
2016-08-20 05:13:41 +08:00
There are a few new bits here. The first is another `use`, bringing a type
called `std::cmp::Ordering` into scope from the standard crate. `Ordering` is
another enum, like `Result`, but the variants for `Ordering` are `Less`,
`Greater`, and `Equal`. These are the three outcomes that are possible when you
compare two things.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
Then we add five new lines at the bottom that use the `Ordering` type:
2016-08-03 10:07:25 +08:00
```rust,ignore
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
```
2016-08-20 05:13:41 +08:00
The `cmp()` method compares two values, and can be called on anything that can
be compared. It takes a reference to the thing you want to compare it to, so
here it's comparing our `guess` to our `secret_number`. `cmp()` returns a
variant of the `Ordering` type we imported with the `use` statement earlier. We
use a `match` statement to decide what to do next based on which
variant of `Ordering` we got back from our call to `cmp()` with the values in
`guess` and `secret_number`.
`match` statements are made up of *arms*. An arm consists of a *pattern* and
the code that we should run if the value given to the beginning of the `match`
statement fits that arm's pattern. Rust takes the value given to `match` and
looks through each arm's pattern in turn. The `match` construct and patterns
are powerful features in Rust that will be covered in detail in Chapter XX and
Chapter XX, respectively.
Let's walk through an example of what would happen with our `match`. Say that
the user has guessed 50, and the randomly-generated secret number this time
is 38. So when we compare 50 to 38, the `cmp()` method will return
`Ordering::Greater`, since 50 is greater than 38. `Ordering::Greater` is the
value that the `match` statement gets. It looks at the first arm's pattern,
`Ordering::Less`, and says nope, the value we have (`Ordering::Greater`) does
not match `Ordering::Less`. So it ignores the code in that arm and moves on to
the next arm. The next arm's pattern, `Ordering::Greater`, **does** match
`Ordering::Greater`! The associated code in that arm will get executed, which
prints "Too big!" to the screen. Then we're done with the `match` statement; we
don't look at the last arm at all in this particular scenario.
However, this code wont quite compile yet. Lets try it:
2016-08-03 10:07:25 +08:00
```bash
$ cargo build
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
src/main.rs:23:21: 23:35 error: mismatched types [E0308]
src/main.rs:23 match guess.cmp(&secret_number) {
^~~~~~~~~~~~~~
src/main.rs:23:21: 23:35 help: run `rustc --explain E0308` to see a detailed explanation
src/main.rs:23:21: 23:35 note: expected type `&std::string::String`
src/main.rs:23:21: 23:35 note: found type `&_`
error: aborting due to previous error
Could not compile `guessing_game`.
```
2016-08-20 05:13:41 +08:00
Whew! This is a big error. The core of the error says that we have *mismatched
types*. Rust has a strong, static type system. However, it also has type
inference. When we wrote `let guess = String::new()`, Rust was able to infer
that `guess` should be a `String` and didnt make us write the type out. Our
`secret_number` on the other hand is a number type. There are a few number
types which can have a value between one and a hundred: `i32`, a thirty-two-bit
number; or `u32`, an unsigned thirty-two-bit number; `i64`, a sixty-four-bit
number; or others. Rust defaults to an `i32`, so that's the type of
`secret_number`. The error is because Rust will not compare two different types.
2016-08-03 10:07:25 +08:00
Ultimately, we want to convert the `String` we read as input
into a real number type so that we can compare it to the guess numerically. We
2016-08-20 05:13:41 +08:00
can do that with two more lines; add this to your program:
Filename: src/main.rs
2016-08-03 10:07:25 +08:00
```rust,ignore
extern crate rand;
use std::io;
use std::cmp::Ordering;
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: 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!"),
}
}
```
2016-08-20 05:13:41 +08:00
The two new lines are:
2016-08-03 10:07:25 +08:00
```rust,ignore
let guess: u32 = guess.trim().parse()
.expect("Please type a number!");
```
2016-08-20 05:13:41 +08:00
We create a variable binding `guess`. But wait a minute, don't we already have
a variable binding named `guess`? We do, but Rust allows us to *shadow* the
previous value of `guess` with a new one. This is often used in this exact
situation, where we want to convert a value from one type into another type.
Shadowing lets us re-use the `guess` variable name rather than forcing us to
come up with two unique bindings, like `guess_str` and `guess` or something.
We bind `guess` to the expression `guess.trim().parse()`.
The `guess` in the expression refers to the original `guess` that was a
`String` with our input in it. The `trim()` method on `String`s will eliminate
any white space at the beginning and end. Our u32 can only contain numerical
characters, but we have to press the "return" key to satisfy `read_line()`.
When we press the return key, it introduces a newline character. For example,
if we type `5` and hit return, `guess` looks like this: `5\n`. The `\n`
represents "newline", the return key. The `trim()` method gets rid of this,
leaving our string with only the `5`.
The `parse()` method on strings parses a string into some kind of
number. Since this method can parse a variety of number types, we need to tell
Rust the exact type of number we want with `let guess: u32`. The colon (`:`)
after `guess` tells Rust were going to annotate its type. Rust has a few
built-in number types, but weve chosen `u32`, an unsigned, thirty-two bit
integer. Its a good default choice for a small positive number. You'll see the
other number types in Chapter XX.
Our call to `parse()` could quite easily cause an error, if, for example, our
string contained `A👍%`; thered be no way to convert that to a number. Because
it might fail, the `parse()` method returns a `Result` type, much like the
`read_line()` method does that we discussed earlier. We're going to treat this
`Result` the same way by using the `expect()` method again. If `parse()`
returns an `Err` `Result` variant because it could not create a number from the
string, the `expect()` call will crash the game and print the message we give
it. If `parse()` can successfully turn the string into a number, it will return
the `Ok` variant of `Result`, and `expect()` will return the number that we
want that it will take out of the `Ok` value for us.
2016-08-03 10:07:25 +08:00
Lets try our program out!
```bash
$ cargo run
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
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 we even added spaces before our guess, and it still figured
2016-08-20 05:13:41 +08:00
out that we 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.
2016-08-03 10:07:25 +08:00
Now weve got most of the game working, but we can only make one guess. Lets
2016-08-20 05:13:41 +08:00
change that by adding a loop!
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
## Allowing Multiple Guesses with Looping
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
The `loop` keyword gives us an infinite loop. We'll add that in to give us more
chances at guessing the number:
Filename: src/main.rs
2016-08-03 10:07:25 +08:00
```rust,ignore
extern crate rand;
use std::io;
use std::cmp::Ordering;
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: 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!"),
}
}
}
```
2016-08-20 05:13:41 +08:00
As you can see, we've moved everything from the guess input onwards into a
loop. Make sure to indent those lines another four spaces each, and try it out.
You'll notice we have a new problem because the program is doing exactly what we
told it to do: ask for another guess forever! It doesn't seem like we can quit!
We could always halt the program by using the keyboard shortcut `control-c`.
There's another way to escape the monster we've created that will infinitely
demand more guesses, though, that can be found in our discussion about
`parse()`: if we give a non-number answer, the program will crash. We can use
that to quit! Observe:
2016-08-03 10:07:25 +08:00
```bash
$ cargo run
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
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
thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/libcore/result.rs:785
note: Run with `RUST_BACKTRACE=1` for a backtrace.
error: Process didn't exit successfully: `target/debug/guess` (exit code: 101)
```
2016-08-20 05:13:41 +08:00
This method means that typing `quit` actually quits the game, but so does any
other non-number input. This is suboptimal, to say the least. We want the game
to automatically stop when the correct number is guessed.
#### Quitting When you Win
Lets program the game to quit when you win by adding a `break` in that case:
Filename: src/main.rs
2016-08-03 10:07:25 +08:00
```rust,ignore
extern crate rand;
use std::io;
use std::cmp::Ordering;
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: 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!");
break;
}
}
}
}
```
2016-08-20 05:13:41 +08:00
By adding the `break` line after `You win!`, well exit the loop when we guses
the secret number correctly. Exiting the loop also means exiting the program,
since the loop is the last thing in `main()`.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
#### Handling Invalid Input
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
For our final refinement of the game's behavior, rather than crashing the
program when someone inputs a non-number, we want the game to ignore it so we
can continue guessing. We can do that by altering the line where we convert
`guess` from a `String` to a `u32`:
2016-08-03 10:07:25 +08:00
```rust,ignore
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
```
2016-08-20 05:13:41 +08:00
This is how you generally move from "crash on error" to "actually handle the
error": by switching from an `expect()` statement to a `match` statement.
Remember that `parse()` returns a `Result` type, and `Result` is an enum that
has the variants `Ok` or `Err`. We're going to use a `match` statement here,
like we did with the `Ordering` result of the `cmp()` method.
If `parse()` is able to successfully turn the string into a number, it will
return an `Ok` value that contains the resulting number. That `Ok` value will
match the first arm's pattern, and the match statement will just return the
`num` value that `parse()` produced and put inside the `Ok` value. That number
will end up right where we want it, in the new `guess` binding we're creating.
2016-08-03 10:07:25 +08:00
2016-08-20 05:13:41 +08:00
If `parse()` is *not* able to turn the string into a number, it will return an
`Err` value that contains more information about the error. The `Err` value
does not match the `Ok(num)` pattern in the first match arm, but it does match
the `Err(_)` pattern in the second arm. The `_` is a catch-all value; we're
saying we want to match all `Err` values, no matter what information they have
inside them. So we execute the second arm's code, `continue`: this means to go
to the next iteration of the `loop` and ask for another guess. So we have
effectively ignored all errors that `parse()` might hit!
Now everything in our program should work as we expect it to! Lets try it:
2016-08-03 10:07:25 +08:00
```bash
$ cargo run
2016-08-20 05:13:41 +08:00
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
2016-08-03 10:07:25 +08:00
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 your guess.
61
You guessed: 61
You win!
```
2016-08-20 05:13:41 +08:00
Awesome! With one tiny last tweak, we can finish the guessing game: we're still
printing out the secret number. That was good for testing, but it kind of ruins
the game. Let's delete the `println!` that outputs the secret number. Heres our
full, final code:
Filename: src/main.rs
2016-08-03 10:07:25 +08:00
```rust,ignore
extern crate rand;
use std::io;
use std::cmp::Ordering;
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: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => 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!");
break;
}
}
}
}
```
## Complete!
At this point, you have successfully built the Guessing Game! Congratulations!
2016-08-20 05:13:41 +08:00
This project was a hands-on way to introduce you to a lot of new Rust concepts:
`let`, `match`, methods, associated functions, using external crates, and more.
In the next few chapters, we will go through these concepts in more detail.
Chapter 3 covers concepts that most programming languages have, like variables,
data types, and functions, and shows how to use them in Rust. Chapter 4 gets
into ownership, which is the feature of Rust that is most different from other
languages. Chapter 5 discusses structs and method syntax, and Chapter 6
endeavors to explain enums.