TRPL: guessing game

This also made me realize that I wasn't using the correct term,
'associated functions', rather than 'static methods'. So I corrected
that in the method syntax chapter.
This commit is contained in:
Steve Klabnik 2015-05-03 13:09:49 -04:00
parent 26933a638c
commit 9d512fdd19
4 changed files with 1011 additions and 5 deletions

View file

@ -5,6 +5,7 @@
* [Hello, world!](hello-world.md)
* [Hello, Cargo!](hello-cargo.md)
* [Learn Rust](learn-rust.md)
* [Guessing Game](guessing-game.md)
* [Effective Rust](effective-rust.md)
* [The Stack and the Heap](the-stack-and-the-heap.md)
* [Testing](testing.md)

View file

@ -0,0 +1,999 @@
% Guessing Game
For our first project, well 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. Sounds good?
# Set up
Lets 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. Lets give it a shot:
```bash
$ 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 were making a binary, rather than a library.
Check out the generated `Cargo.toml`:
```toml
[package]
name = "guessing_game"
version = "0.0.1"
authors = ["Your Name <you@example.com>"]
```
Cargo gets this information from your environment. If its not correct, go ahead
and fix that.
Finally, Cargo generated a Hello, world! for us. Check out `src/main.rs`:
```rust
fn main() {
println!("Hello, world!")
}
```
Lets try compiling what Cargo gave us:
```{bash}
$ cargo build
Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
```
Excellent! Open up your `src/main.rs` again. Well be writing all of
our code in this file.
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:
```bash
$ cargo run
Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
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 just such a project, we need to quickly test each
iteration before moving on to the next one.
# Processing a Guess
Lets 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`:
```rust,no_run
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
let input = io::stdin().read_line(&mut guess)
.ok()
.expect("Failed to read line");
println!("You guessed: {}", input);
}
```
Theres a lot here! Lets go over it, bit by bit.
```rust,ignore
use std::io;
```
Well need to take user input, and then print the result as output. As such, we
need the `io` library from the standard library. Rust only imports a few things
into every program, [the prelude][prelude]. If its not in the prelude,
youll have to `use` it directly.
[prelude]: ../std/prelude/index.html
```rust,ignore
fn main() {
```
As youve seen before, the `main()` function is the entry point into your
program. The `fn` syntax declares a new function, the `()`s indicate that
there are no arguments, and `{` starts the body of the function. Because
we didnt include a return type, its assumed to be `()`, an empty
[tuple][tuples].
[tuples]: primitive-types.html#tuples
```rust,ignore
println!("Guess the number!");
println!("Please input your guess.");
```
We previously learned that `println!()` is a [macro][macros] that
prints a [string][strings] to the screen.
[macros]: macros.html
[strings]: strings.html
```rust,ignore
let mut guess = String::new();
```
Now were getting interesting! Theres a lot going on in this little line. The first thing to notice is that this is a [let statement][let], which is used to create variable bindings. They take this form:
```rust,ignore
let foo = bar;
```
[let]: variable-bindings.html
This will create a new binding named `foo`, and bind it to the value `bar`. In
many languages, this is called a variable, but Rusts variable bindings have
a few tricks up their sleeves.
For example, theyre [immutable][immutable] by default. Thats why our example
uses `mut`: it makes a binding mutable, rather than immutable. `let` doesnt
take a name on the left hand side, it actually accepts a
[pattern][patterns]. Well use patterns more later. Its easy enough
to use for now:
```
let foo = 5; // immutable.
let mut bar = 5; // mutable
```
[immutable]: mutability.html
[patterns]: patterns.html
Oh, and `//` will start a comment, until the end of the line. Rust ignores
everything in [comments][comments].
[comments]: comments.html
So now we know that `let mut guess` will introduce a mutable binding named
`guess`, but we have to look at the other side of the `=` for what its
bound to: `String::new()`.
`String` is a string type, provided by the standard library. A
[`String`][string] is a growable, UTF-8 encoded bit of text.
[string]: ../std/string/struct.String.html
The `::new()` syntax is uses `::` because this is an associated function of
a particular type. That is to say, its associated with `String` itself,
rather than a particular instance of a `String`. Some languages call this a
static method.
This function is named `new()`, because it creates a new, empty `String`.
Youll find a `new()` function on many types, as its a common name for making
a new value of some kind.
Lets move forward:
```rust,ignore
io::stdin().read_line(&mut guess)
.ok()
.expect("Failed to read line");
```
Thats a lot more! Lets go bit-by-bit. The first line has two parts. Heres
the first:
```rust,ignore
io::stdin()
```
Remember how we `use`d `std::io` on the first line of the program? Were now
calling an associated function on it. If we didnt `use std::io`, we could
have written this line as `std::io::stdin()`.
This particular function returns a handle to the standard input for your
terminal. More specifically, a [std::io::Stdin][iostdin].
[iostdin]: ../std/io/struct.Stdin.html
The next part will use this handle to get input from the user:
```rust,ignore
.read_line(&mut guess)
```
Here, we call the [`read_line()`][read_line] method on our handle.
[Method][method]s are like associated functions, but are only available on a
particular instance of a type, rather than the type itself. Were also passing
one argument to `read_line()`: `&mut guess`.
[read_line]: ../std/io/struct.Stdin.html#method.read_line
[method]: methods.html
Remember how we bound `guess` above? We said it was mutable. However,
`read_line` doesnt take a `String` as an argument: it takes a `&mut String`.
Rust has a feature called [references][references], which allows you to have
multiple references to one piece of data, which can reduce copying. References
are a complex feature, as one of Rusts major selling points 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. 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`.
Why does `read_line()` take a mutable reference to a string? Its job is
to take what the user types into standard input, and place that into a
string. So it takes that string as an argument, and in order to add
the input, it needs to be mutable.
[references]: references-and-borrowing.html
But were not quite done with this line of code, though. While its
a single line of text, its only the first part of the single logical line of
code:
```rust,ignore
.ok()
.expect("Failed to read line");
```
When you call a method with the `.foo()` syntax, you may introduce a newline
and other whitespace. This helps you split up long lines. We _could_ have
done:
```rust,ignore
io::stdin().read_line(&mut guess).ok().expect("failed to read line");
```
But that gets hard to read. So weve split it up, three lines for three
method calls. We already talked about `read_line()`, but what about `ok()`
and `expect()`? Well, we already mentioned that `read_line()` puts what
the user types into the `&mut String` we pass it. But it also returns
a value: in this case, an [`io::Result`][ioresult]. Rust has a number of
types named `Result` in its standard library: a generic [`Result`][result],
and then specific versions for sub-libraries, like `io::Result`.
[ioresult]: ../std/io/type.Result.html
[result]: ../std/result/enum.Result.html
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
this case, `io::Result` has an `ok()` method, which says we want to assume
this value is a successful one. If not, just throw away the error
information. Why throw it away? Well, for a basic program, we just want to
print a generic error, as basically any issue means we cant continue. The
[`ok()` method][ok] returns a value which has another method defined on it:
`expect()`. The [`expect()` method][expect] takes a value its called on, and
if it isnt a successful one, [`panic!`][panic]s with a message you passed you
passed it. A `panic!` like this will cause our program to crash, displaying
the message.
[ok]: ../std/result/enum.Result.html#method.ok
[expect]: ../std/option/enum.Option.html#method.expect
[panic]: error-handling.html
If we leave off calling these two methods, our program will compile, but
well get a warning:
```bash
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
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);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
Rust warns us that we havent used the `Result` value. This warning comes from
a special annotation that `io::Result` has. Rust is trying to tell you that
you havent handled a possible error. The right way to suppress the error is
to actually write error handling. Luckily, if we just want to crash if theres
a problem, we can use these two little methods. If we can recover from the
error somehow, wed do something else, but well save that for a future
project.
Theres just one line of this first example left:
```rust,ignore
println!("You guessed: {}", input);
}
```
This prints out the string we saved our input in. The `{}`s are a placeholder,
and so we pass it `input` as an argument. If we had multiple `{}`s, we would
pass multiple arguments:
```rust
let x = 5;
let y = 10;
println!("x and y: {} and {}", x, y);
```
Easy.
Anyway, thats the tour. We can run what we have with `cargo run`:
```bash
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
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.
# Generating a secret number
Next, we need to generate a secret number. Rust does not yet include random
number functionality in its standard library. The Rust team does, however,
provide a [`rand` crate][randcrate]. A crate is a package of Rust code.
Weve been building a binary crate, which is an executable. `rand` is a
library crate, which contains code thats intended to be used with other
programs.
[randcrate]: https://crates.io/crates/rand
Using external crates is where Cargo really shines. Before we can write
the code using `rand`, we need to modify our `Cargo.toml`. Open it up, and
add these few lines at the bottom:
```toml
[dependencies]
rand="0.3.0"
```
The `[dependencies]` section of `Cargo.toml` is like the `[package]` section:
everything that follows it is part of it, until the next section starts.
Cargo uses the dependencies section to know what dependencies on external
crates you have, and what versions you require. In this case, weve used `*`,
which means that well use the latest version of `rand`. Cargo understands
[Semantic Versioning][semver], which is a standard for writing version
numbers. If we wanted a specific version or range of versions, we could be
more specific here. [Cargos documentation][cargodoc] contains more details.
[semver]: http://semver.org
[cargodoc]: http://doc.crates.io/crates-io.html
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.8
Downloading libc v0.1.6
Compiling libc v0.1.6
Compiling rand v0.3.8
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
```
(You may see different versions, of course.)
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
[Crates.io][cratesio]. Crates.io is where people in the Rust ecosystem
post their open source Rust projects for others to use.
[cratesio]: https://crates.io
After updating the registry, Cargo checks our `[dependencies]` and downloads
any we dont have yet. In this case, while we only said we wanted to depend on
`rand`, weve also grabbed a copy of `libc`. This is because `rand` depends on
`libc` to work. After downloading them, it compiles them, and then compiles
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, and that
all of its dependencies are built, and so theres no reason to do all that
stuff. With nothing to do, it simply exits. If we open up `src/main.rs` again,
make a trivial change, and then save it again, well just see one line:
```bash
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
```
So, we told Cargo we wanted any version of `rand`, and so it fetched the
latest version at the time this was written, `v0.3.8`. But what happens
when next week, version `v0.4.0` comes out, which changes something with
`rand`, and it includes a breaking change? After all, a `v0.y.z` version
in SemVer can change every release.
The answer to this problem is the `Cargo.lock` file youll now find in your
project directory. When you build your project for the first time, Cargo
figures out all of the versions that fit your criteria, and 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 then use that specific version
rather than do all the work of figuring out versions again. This lets you
have a repeatable build automatically.
What about when we _do_ want to use `v0.4.0`? Cargo has another command,
`update`, which says ignore the lock, figure out all the latest versions that
fit what weve specified. If that works, write those versions out to the lock
file.
Theres a lot more to say about [Cargo][doccargo] and [its
ecosystem][doccratesio], but for now, thats all we need to know. Cargo makes
it really easy to re-use libraries, and so Rustaceans tend to write smaller
projects which are assembled out of a number of sub-packages.
[doccargo]: http://doc.crates.io
[doccratesio]: http://doc.crates.io/crates-io.html
Lets get on to actually _using_ `rand`. Heres our next step:
```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)
.ok()
.expect("failed to read line");
println!("You guessed: {}", guess);
}
```
The first thing weve done is change the first line. It now says
`extern crate rand`. Because we declared `rand` in our `[dependencies]`, we
can use `extern crate` to let Rust know well be making use of it. This also
does the equivalent of a `use rand;` as well, so we can make use of anything
in the `rand` crate by prefixing it with `rand::`.
Next, we added another `use` line: `use rand::Rng`. Were going to use a
method in a moment, and it requires that `Rng` be in scope to work. The basic
idea is this: methods are defined on something called traits, and for the
method to work, it needs the trait to be in scope. For more about the
details, read the [traits][traits] section.
[traits]: traits.html
There are two other lines we added, in the middle:
```rust,ignore
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is: {}", secret_number);
```
We use the `rand::thread_rng()` function to get a copy of the random number
generator, which is local to the particular [thread][concurrency] of execution
were in. Because we `use rand::Rng`d above, it has a `gen_range()` method
available. This method takes two arguments, and generates a number between
them. Its inclusive on the lower bound, but exclusive on the upper bound,
so we need `1` and `101` to get a number between one and a hundred.
[concurrency]: concurrency.html
The second line just prints out the secret number. This is useful while
were developing our program, so we can 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!
Try running our new program a few times:
```bash
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
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
```
Great! Next up: lets compare our guess to the secret guess.
# Comparing guesses
Now that weve got user input, lets compare our guess to the random guess.
Heres our next step, though it doesnt quite work yet:
```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)
.ok()
.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!"),
}
}
```
A few new bits here. The first is another `use`. We bring a type called
`std::cmp::Ordering` into scope. Then, five new lines at the bottom that use
it:
```rust,ignore
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
```
The `cmp()` method can be called on anything that can be compared, and it
takes a reference to the thing you want to compare it to. It returns the
`Ordering` type we `use`d earlier. We use a [`match`][match] statement to
determine exactly what kind of `Ordering` it is. `Ordering` is an
[`enum`][enum], short for enumeration, which looks like this:
```rust
enum Foo {
Bar,
Baz,
}
```
[match]: match.html
[enum]: enums.html
With this definition, anything of type `Foo` can be either a
`Foo::Bar` or a `Foo::Baz`. We use the `::` to indicate the
namespace for a particular `enum` variant.
The [`Ordering`][ordering] enum has three possible variants: `Less`, `Equal`,
and `Greater`. The `match` statement takes a value of a type, and lets you
create an arm for each possible value. Since we have three types of
`Ordering`, we have three arms:
```rust,ignore
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
```
[ordering]: ../std/cmp/enum.Ordering.html
If its `Less`, we print `Too small!`, if its `Greater`, `Too big!`, and if
`Equal`, `You win!`. `match` is really useful, and is used often in Rust.
I did mention that this wont quite work yet, though. Lets try it:
```bash
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
src/main.rs:28:21: 28:35 error: mismatched types:
expected `&collections::string::String`,
found `&_`
(expected struct `collections::string::String`,
found integral variable) [E0308]
src/main.rs:28 match guess.cmp(&secret_number) {
^~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `guessing_game`.
```
Whew! This is a big error. The core of it is 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 so it doesnt make us write out the type. And with
our `secret_number`, there are a number of 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, or `i64`, a sixty-four-bit number. Or others.
So far, that hasnt mattered, and so Rust defaults to an `i32`. However, here,
Rust doesnt know how to compare the `guess` and the `secret_number`. They
need to be the same type. Ultimately, we want to convert the `String` we
read as input into a real number type, for comparison. We can do that
with three more lines. Heres our new program:
```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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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!"),
}
}
```
The new three lines:
```rust,ignore
let guess: u32 = guess.trim().parse()
.ok()
.expect("Please type a number!");
```
Wait a minute, I thought we already had a `guess`? We do, but Rust allows us
to shadow the previous `guess` with a new one. This is often used in this
exact situation, where `guess` starts as a `String`, but we want to convert it
to an `u32`. Shadowing lets us re-use the `guess` name, rather than forcing us
to come up with two unique names like `guess_str` and `guess`, or something
else.
We bind `guess` to an expression that looks like something we wrote earlier:
```rust,ignore
guess.trim().parse()
```
Followed by an `ok().expect()` invocation. Here, `guess` refers to the old
`guess`, the one 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 of
our string. This is important, as we had to press the return key to satisfy
`read_line()`. This means that if we type `5` and hit return, `guess` looks
like this: `5\n`. The `\n` represents newline, the enter key. `trim()` gets
rid of this, leaving our string with just the `5`. The [`parse()` method on
strings][parse] parses a string into some kind of number. Since it can parse a
variety of numbers, we need to give Rust a hint as to the exact type of number
we want. Hence, `let guess: u32`. The colon (`:`) after `guess` tells Rust
were going to annotate its type. `u32` is an unsigned, thirty-two bit
integer. Rust has [a number of built-in number types][number], but weve
chosen `u32`. Its a good default choice for a small positive numer.
[parse]: ../std/primitive.str.html#method.parse
[number]: primitive-types.html#numeric-types
Just like `read_line()`, our call to `parse()` could cause an error. What if
our string contained `A👍%`? Thered be no way to convert that to a number. As
such, well do the same thing we did with `read_line()`: use the `ok()` and
`expect()` methods to crash if theres an error.
Lets try our program out!
```bash
$ 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.
Now weve got most of the game working, but we can only make one guess. Lets
change that by adding loops!
# Looping
The `loop` keyword gives us an infinite loop. Lets add that in:
```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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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!"),
}
}
}
```
And try it out. But wait, didnt we just add an infinite loop? Yup. Remember
our discussion about `parse()`? If we give a non-number answer, well `return`
and quit. Observe:
```bash
$ 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
thread '<main>' panicked at 'Please type a number!'
```
Ha! `quit` actually quits. As does any other non-number input. Well, this is
suboptimal to say the least. First, lets actually quit when you win the game:
```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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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;
}
}
}
}
```
By adding the `break` line after the `You win!`, well exit the loop when we
win. Exiting the loop also means exiting the program, since its the last
thing in `main()`. We have just one more tweak to make: when someone inputs a
non-number, we dont want to quit, we just want to ignore it. We can do that
like this:
```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)
.ok()
.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;
}
}
}
}
```
These are the lines that changed:
```rust,ignore
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
```
This is how you generally move from crash on error to actually handle the
error, by switching from `ok().expect()` to a `match` statement. The `Result`
returned by `parse()` is an enum just like `Ordering`, but in this case, each
variant has some data associated with it: `Ok` is a success, and `Err` is a
failure. Each contains more information: the successful parsed integer, or an
error type. In this case, we `match` on `Ok(num)`, which sets the inner value
of the `Ok` to the name `num`, and then we just return it on the right-hand
side. In the `Err` case, we dont care what kind of error it is, so we just
use `_` intead of a name. This ignores the error, and `continue` causes us
to go to the next iteration of the `loop`.
Now we should be good! Lets try:
```bash
$ 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 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? Thats right, we dont want to print out the secret
number. It was good for testing, but it kind of ruins the game. Heres our
final source:
```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)
.ok()
.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!
This first project showed you a lot: `let`, `match`, methods, associated
functions, using external crates, and more. Our next project will show off
even more.

View file

@ -1,4 +1,9 @@
% Learn Rust
This section is coming soon! It will eventually have a few tutorials with
building real Rust projects, but they are under development.
Welcome! This section has a few tutorials that teach you Rust through building
projects. Youll get a high-level overview, but well skim over the details.
If youd prefer a more from the ground up-style experience, check
out [Syntax and Semantics][ss].
[ss]: syntax-and-semantics.html

View file

@ -154,9 +154,10 @@ fn main() {
}
```
This static method builds a new `Circle` for us. Note that static methods
are called with the `Struct::method()` syntax, rather than the `ref.method()`
syntax.
This associated function builds a new `Circle` for us. Note that associated
functions are called with the `Struct::function()` syntax, rather than the
`ref.method()` syntax. Some other langauges call associated functions static
methods.
# Builder Pattern