mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-01-23 15:40:27 +08:00
First third to nostarch!
This commit is contained in:
parent
92085ebd8f
commit
bdc30d4090
492
nostarch/chapter01.md
Normal file
492
nostarch/chapter01.md
Normal file
@ -0,0 +1,492 @@
|
||||
# Introduction
|
||||
|
||||
Welcome to “The Rust Programming Language”, an introductory book about Rust.
|
||||
Rust is a programming language that’s focused on safety, speed, and
|
||||
concurrency. Its design lets you create programs that have the performance and
|
||||
control of a low-level language, but with helpful abstractions that feel like a
|
||||
high-level language. The Rust community welcomes all programmers who have their
|
||||
experience in languages like C and are looking for a safer alternative, as well
|
||||
as programmers from languages like Python who are looking for ways to write more
|
||||
performant code without losing expressiveness.
|
||||
|
||||
Rust provides the majority of its safety checks at compile time and without a
|
||||
garbage collector so that your program's runtime isn't impacted. This makes it
|
||||
useful in a number of use cases that other languages aren’t good at: embedding
|
||||
in other languages, programs with specific space and time requirements, and
|
||||
writing low-level code, like device drivers and operating systems. It's also
|
||||
great for web applications: it powers the Rust package registry site, crates.io!
|
||||
We're excited to see what _you_ create with Rust.
|
||||
|
||||
This book is written for a reader who already knows how to program in at least
|
||||
one programming language. After reading this book, you should be comfortable
|
||||
writing Rust programs. We’ll be learning Rust through small, focused examples
|
||||
that build on each other to demonstrate how to use various features of Rust as
|
||||
well as how they work behind the scenes.
|
||||
|
||||
## Contributing to the book
|
||||
|
||||
This book is open source. If you find an error, please don’t hesitate to file an
|
||||
issue or send a pull request [on GitHub].
|
||||
|
||||
[on GitHub]: https://github.com/rust-lang/book
|
||||
|
||||
## Installation
|
||||
|
||||
The first step to using Rust is to install it. You’ll need an internet
|
||||
connection to run the commands in this chapter, as we’ll be downloading Rust
|
||||
from the internet.
|
||||
|
||||
We’ll be showing off a number of commands using a terminal, and those lines all
|
||||
start with `$`. You don't need to type in the `$`s; they are there to indicate
|
||||
the start of each command. You’ll see many tutorials and examples around the web
|
||||
that follow this convention: `$` for commands run as a regular user, and `#`
|
||||
for commands you should be running as an administrator. Lines that don't start
|
||||
with `$` are typically showing the output of the previous command.
|
||||
|
||||
### Installing on Linux or Mac
|
||||
|
||||
If you're on Linux or a Mac, all you need to do is open a terminal and type
|
||||
this:
|
||||
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
This will download a script and start the installation. You may be prompted for
|
||||
your password. If it all goes well, you’ll see this appear:
|
||||
|
||||
```bash
|
||||
Rust is installed now. Great!
|
||||
```
|
||||
|
||||
### Installing on Windows
|
||||
|
||||
If you're on Windows, please download the appropriate [installer][install-page].
|
||||
|
||||
[install-page]: https://www.rust-lang.org/install.html
|
||||
|
||||
### Uninstalling
|
||||
|
||||
Uninstalling Rust is as easy as installing it. On Linux or Mac, just run
|
||||
the uninstall script:
|
||||
|
||||
```bash
|
||||
$ rustup self uninstall
|
||||
```
|
||||
|
||||
If you used the Windows installer, you can re-run the `.msi` and it will give
|
||||
you an uninstall option.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If you've got Rust installed, you can open up a shell, and type this:
|
||||
|
||||
```bash
|
||||
$ rustc --version
|
||||
```
|
||||
|
||||
You should see the version number, commit hash, and commit date in a format
|
||||
similar to this for the latest stable version at the time you install:
|
||||
|
||||
```bash
|
||||
rustc x.y.z (abcabcabc yyyy-mm-dd)
|
||||
```
|
||||
|
||||
If you see this, Rust has been installed successfully!
|
||||
Congrats!
|
||||
|
||||
If you don't and you're on Windows, check that Rust is in your `%PATH%` system
|
||||
variable. If it isn't, run the installer again, select "Change" on the "Change,
|
||||
repair, or remove installation" page and ensure "Add to PATH" is checked.
|
||||
|
||||
If not, there are a number of places where you can get help. The easiest is
|
||||
[the #rust IRC channel on irc.mozilla.org][irc], which you can access through
|
||||
[Mibbit][mibbit]. Click that link, and you'll be chatting with other Rustaceans
|
||||
(a silly nickname we call ourselves) who can help you out. Other great resources
|
||||
include [the user’s forum][users] and [Stack Overflow][stackoverflow].
|
||||
|
||||
[irc]: irc://irc.mozilla.org/#rust
|
||||
[mibbit]: http://chat.mibbit.com/?server=irc.mozilla.org&channel=%23rust
|
||||
[users]: https://users.rust-lang.org/
|
||||
[stackoverflow]: http://stackoverflow.com/questions/tagged/rust
|
||||
|
||||
### Local documentation
|
||||
|
||||
The installer also includes a copy of the documentation locally, so you can
|
||||
read it offline. On Linux or Mac, run `rustup doc` to open the local
|
||||
documentation in your browser. On Windows, the documentation is in a
|
||||
`share/doc` directory inside the directory where Rust was installed.
|
||||
|
||||
## Hello, World!
|
||||
|
||||
Now that you have Rust installed, let’s write your first Rust program. It's
|
||||
traditional when learning a new language to write a little program to print the
|
||||
text “Hello, world!” to the screen, and in this section, we'll follow that
|
||||
tradition.
|
||||
|
||||
> Note: This book assumes basic familiarity with the command line. Rust itself
|
||||
> makes no specific demands about your editing, tooling, or where your code
|
||||
> lives, so if you prefer an IDE to the command line, that's an option.
|
||||
|
||||
### Creating a Project File
|
||||
|
||||
First, make a file to put your Rust code in. Rust doesn't care where your code
|
||||
lives, but for this book, we'd suggest making a *projects* directory in your
|
||||
home directory and keeping all your projects there. Open a terminal and enter
|
||||
the following commands to make a directory for this particular project:
|
||||
|
||||
```bash
|
||||
$ mkdir ~/projects
|
||||
$ cd ~/projects
|
||||
$ mkdir hello_world
|
||||
$ cd hello_world
|
||||
```
|
||||
|
||||
> Note: If you’re on Windows and not using PowerShell, the `~` that represents
|
||||
> your home directory may not work.
|
||||
> Consult the documentation for your shell for more details.
|
||||
|
||||
### Writing and Running a Rust Program
|
||||
|
||||
Next, make a new source file and call it *main.rs*. Rust files always end with
|
||||
the *.rs* extension. If you’re using more than one word in your filename, use
|
||||
an underscore to separate them. For example, you'd use *hello_world.rs* rather
|
||||
than *helloworld.rs*.
|
||||
|
||||
Now open the *main.rs* file you just created, and type the following code:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Save the file, and go back to your terminal window. On Linux or OSX, enter the
|
||||
following commands:
|
||||
|
||||
```bash
|
||||
$ rustc main.rs
|
||||
$ ./main
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
On Windows, just replace `main` with `main.exe`. Regardless of your operating
|
||||
system, you should see the string `Hello, world!` print to the terminal. If you
|
||||
did, then congratulations! You've officially written a Rust program. That makes
|
||||
you a Rust programmer! Welcome.
|
||||
|
||||
### Anatomy of a Rust Program
|
||||
|
||||
Now, let’s go over what just happened in your "Hello, world!" program in
|
||||
detail. Here's the first piece of the puzzle:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
These lines define a *function* in Rust. The `main` function is special: it's
|
||||
the first thing that is run for every executable Rust program. The first line
|
||||
says, “I’m declaring a function named `main` that takes no arguments and
|
||||
returns nothing.” If there were arguments, they would go inside the parentheses
|
||||
(`(` and `)`). We aren’t returning anything from this function, so we have
|
||||
omitted the return type entirely. If there was a return type, there would be a
|
||||
`->` and the return type after the parentheses.
|
||||
|
||||
Also note that the function body is wrapped in curly braces (`{` and `}`). Rust
|
||||
requires these around all function bodies. It's considered good style to put
|
||||
the opening curly brace on the same line as the function declaration, with one
|
||||
space in between.
|
||||
|
||||
Inside the `main()` function:
|
||||
|
||||
```rust
|
||||
println!("Hello, world!");
|
||||
```
|
||||
|
||||
This line does all of the work in this little program: it prints text to the
|
||||
screen. There are a number of details that are important here. The first is
|
||||
that it’s indented with four spaces, not tabs.
|
||||
|
||||
The second important part is `println!()`. This is calling a Rust *macro*,
|
||||
which is how metaprogramming is done in Rust. If it were calling a function
|
||||
instead, it would look like this: `println()` (without the `!`). We'll discuss
|
||||
Rust macros in more detail in Chapter XX, but for now you just need to know
|
||||
that when you see a `!` that means that you’re calling a macro instead of a
|
||||
normal function.
|
||||
|
||||
Next is `"Hello, world!"` which is a *string*. We pass this string as an
|
||||
argument to `println!()`, which prints the string to the screen. Easy enough!
|
||||
|
||||
The line ends with a semicolon (`;`). The `;` indicates that this expression is
|
||||
over, and the next one is ready to begin. Most lines of Rust code end with a
|
||||
`;`.
|
||||
|
||||
### Compiling and Running Are Separate Steps
|
||||
|
||||
In "Writing and Running a Rust Program", we showed you how to run a newly
|
||||
created program. We'll break that process down and examine each step now.
|
||||
|
||||
Before running a Rust program, you have to compile it. You can use the Rust
|
||||
compiler by entering the `rustc` command and passing it the name of your source
|
||||
file, like this:
|
||||
|
||||
```bash
|
||||
$ rustc main.rs
|
||||
```
|
||||
|
||||
If you come from a C or C++ background, you'll notice that this is similar to
|
||||
`gcc` or `clang`. After compiling successfully, Rust should output a binary
|
||||
executable, which you can see on Linux or OSX by entering the `ls` command in
|
||||
your shell as follows:
|
||||
|
||||
```bash
|
||||
$ ls
|
||||
main main.rs
|
||||
```
|
||||
|
||||
On Windows, you'd enter:
|
||||
|
||||
```bash
|
||||
$ dir
|
||||
main.exe main.rs
|
||||
```
|
||||
|
||||
This shows we have two files: the source code, with the `.rs` extension, and the
|
||||
executable (`main.exe` on Windows, `main` everywhere else). All that's left to
|
||||
do from here is run the `main` or `main.exe` file, like this:
|
||||
|
||||
```bash
|
||||
$ ./main # or main.exe on Windows
|
||||
```
|
||||
|
||||
If *main.rs* were your "Hello, world!" program, this would print `Hello,
|
||||
world!` to your terminal.
|
||||
|
||||
If you come from a dynamic language like Ruby, Python, or JavaScript, you may
|
||||
not be used to compiling and running a program being separate steps. Rust is an
|
||||
*ahead-of-time compiled* language, which means that you can compile a program,
|
||||
give it to someone else, and they can run it even without having Rust
|
||||
installed. If you give someone a `.rb`, `.py`, or `.js` file, on the other
|
||||
hand, they need to have a Ruby, Python, or JavaScript implementation installed
|
||||
(respectively), but you only need one command to both compile and run your
|
||||
program. Everything is a tradeoff in language design.
|
||||
|
||||
Just compiling with `rustc` is fine for simple programs, but as your project
|
||||
grows, you'll want to be able to manage all of the options your project has
|
||||
and make it easy to share your code with other people and projects. Next, we'll
|
||||
introduce you to a tool called Cargo, which will help you write real-world Rust
|
||||
programs.
|
||||
|
||||
## Hello, Cargo!
|
||||
|
||||
Cargo is Rust’s build system and package manager, and Rustaceans use Cargo to
|
||||
manage their Rust projects because it makes a lot of tasks easier. For example,
|
||||
Cargo takes care of building your code, downloading the libraries your code
|
||||
depends on, and building those libraries. We call libraries your code needs
|
||||
‘dependencies’ since your code depends on them.
|
||||
|
||||
The simplest Rust programs, like the one we've written so far, don’t have any
|
||||
dependencies, so right now, you'd only be using the part of Cargo that can take
|
||||
care of building your code. As you write more complex Rust programs, you’ll
|
||||
want to add dependencies, and if you start off using Cargo, that will be a lot
|
||||
easier to do.
|
||||
|
||||
As the vast, vast majority of Rust projects use Cargo, we will assume that
|
||||
you’re using it for the rest of the book. Cargo comes installed with Rust
|
||||
itself, if you used the official installers as covered in the Installation
|
||||
chapter. If you installed Rust through some other means, you can check if you
|
||||
have Cargo installed by typing the following into your terminal:
|
||||
|
||||
```bash
|
||||
$ cargo --version
|
||||
```
|
||||
|
||||
If you see a version number, great! If you see an error like `command not
|
||||
found`, then you should look at the documentation for the way you installed
|
||||
Rust to determine how to install Cargo separately.
|
||||
|
||||
### Creating a Project with Cargo
|
||||
|
||||
Let's create a new project using Cargo and look at how it differs from our
|
||||
project in `hello_world`. Go back to your projects directory (or wherever you
|
||||
decided to put your code):
|
||||
|
||||
```bash
|
||||
$ cd ~/projects
|
||||
```
|
||||
|
||||
And then run:
|
||||
|
||||
```bash
|
||||
$ cargo new hello_cargo --bin
|
||||
$ cd hello_cargo
|
||||
```
|
||||
|
||||
We passed the `--bin` argument to `cargo new` because our goal is to make an
|
||||
executable application, as opposed to a library. Executables are often called
|
||||
*binaries* (as in `/usr/bin`, if you’re on a Unix system). `hello_cargo` is the
|
||||
name we've chosen for our project, and Cargo creates its files in a directory
|
||||
of the same name that we can then go into.
|
||||
|
||||
If we list the files in the `hello_cargo` directory, we can see that Cargo has
|
||||
generated two files and one directory for us: a `Cargo.toml` and a *src*
|
||||
directory with a *main.rs* file inside. It has also initialized a new `git`
|
||||
repository in the `hello_cargo` directory for us; you can change this to use a
|
||||
different version control system, or no version control system, by using the
|
||||
`--vcs` flag.
|
||||
|
||||
Open up `Cargo.toml` in your text editor of choice. It should look something
|
||||
like this:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "hello_cargo"
|
||||
version = "0.1.0"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[dependencies]
|
||||
```
|
||||
|
||||
This file is in the *[TOML]* (Tom's Obvious, Minimal Language) format. TOML is
|
||||
similar to INI but has some extra goodies and is used as Cargo’s
|
||||
configuration format.
|
||||
|
||||
[TOML]: https://github.com/toml-lang/toml
|
||||
|
||||
The first line, `[package]`, is a section heading that indicates that the
|
||||
following statements are configuring a package. As we add more information to
|
||||
this file, we’ll add other sections.
|
||||
|
||||
The next three lines set the three bits of configuration that Cargo needs to
|
||||
see in order to know that it should compile your program: its name, what
|
||||
version it is, and who wrote it. Cargo gets your name and email information
|
||||
from your environment. If it’s not correct, go ahead and fix that and save the
|
||||
file.
|
||||
|
||||
The last line, `[dependencies]`, is the start of a section for you to list any
|
||||
crates that your project will depend on so that Cargo knows to download and
|
||||
compile those too. We won't need any other crates for this project, but we will
|
||||
in the guessing game tutorial in the next chapter.
|
||||
|
||||
Now let's look at `src/main.rs`:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Cargo has generated a "Hello World!" for you, just like the one we wrote
|
||||
earlier! So that part is the same. The differences between our previous project
|
||||
and the project generated by Cargo that we've seen so far are:
|
||||
|
||||
1. Our code goes in the `src` directory
|
||||
2. The top level contains a `Cargo.toml` configuration file
|
||||
|
||||
Cargo expects your source files to live inside the *src* directory so that the
|
||||
top-level project directory is just for READMEs, license information,
|
||||
configuration files, and anything else not related to your code. In this way,
|
||||
using Cargo helps you keep your projects nice and tidy. There's a place for
|
||||
everything, and everything is in its place.
|
||||
|
||||
If you started a project that doesn't use Cargo, as we did with our project in
|
||||
the `hello_world` directory, you can convert it to a project that does use
|
||||
Cargo by moving your code into the `src` directory and creating an appropriate
|
||||
`Cargo.toml`.
|
||||
|
||||
### Building and Running a Cargo Project
|
||||
|
||||
Now let's look at what's different about building and running your Hello World
|
||||
program through Cargo! To do so, enter the following commands:
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
Compiling hello_cargo v0.1.0 (file:///home/yourname/projects/hello_cargo)
|
||||
```
|
||||
|
||||
This should have created an executable file in `target/debug/hello_cargo` (or `target/debug/hello_cargo.exe` on Windows), which you can run with this command:
|
||||
|
||||
```bash
|
||||
$ ./target/debug/hello_cargo # or ./target/debug/hello_cargo.exe on Windows
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
Bam! If all goes well, `Hello, world!` should print to the terminal once more.
|
||||
|
||||
Running `cargo build` for the first time also causes Cargo to create a new file
|
||||
at the top level called *Cargo.lock*, which looks like this:
|
||||
|
||||
```toml
|
||||
[root]
|
||||
name = "hello_cargo"
|
||||
version = "0.1.0"
|
||||
```
|
||||
|
||||
Cargo uses the *Cargo.lock* file to keep track of dependencies in your
|
||||
application. This project doesn't have dependencies, so the file is a bit
|
||||
sparse. Realistically, you won't ever need to touch this file yourself; just
|
||||
let Cargo handle it.
|
||||
|
||||
We just built a project with `cargo build` and ran it with
|
||||
`./target/debug/hello_cargo`, but we can actually do both in one step with
|
||||
`cargo run` as follows:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Running `target/debug/hello_cargo`
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
Notice that this time, we didn't see the output that Cargo was compiling
|
||||
`hello_cargo`. Cargo figured out that the files haven’t changed, so it just ran
|
||||
the binary. If you had modified your source code, Cargo would have rebuilt the
|
||||
project before running it, and you would have seen something like this:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling hello_cargo v0.1.0 (file:///home/yourname/projects/hello_cargo)
|
||||
Running `target/debug/hello_cargo`
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
So a few more differences we've now seen:
|
||||
|
||||
3. Instead of using `rustc`, build a project using `cargo build` (or build and run it in one step with `cargo run`)
|
||||
4. Instead of the result of the build being put in the same directory as our code, Cargo will put it in the `target/debug` directory.
|
||||
|
||||
### Building for Release
|
||||
|
||||
When your project is finally ready for release, you can use `cargo build
|
||||
--release` to compile your project with optimizations. This will create an
|
||||
executable in `target/release` instead of `target/debug`. These optimizations
|
||||
make your Rust code run faster, but turning them on makes your program take
|
||||
longer to compile. This is why there are two different profiles: one for
|
||||
development when you want to be able to rebuild quickly and often, and one for
|
||||
building the final program you’ll give to a user that won't be rebuilt and
|
||||
that we want to run as fast as possible. If you're benchmarking the running
|
||||
time of your code, be sure to run `cargo build --release` and benchmark with
|
||||
the executable in `target/release`.
|
||||
|
||||
### Cargo as Convention
|
||||
|
||||
With simple projects, Cargo doesn't provide a whole lot of value over just
|
||||
using `rustc`, but it will prove its worth as you continue. With complex
|
||||
projects composed of multiple crates, it’s much easier to let Cargo coordinate
|
||||
the build. With Cargo, you can just run `cargo build`, and it should work the
|
||||
right way. Even though this project is simple, it now uses much of the real
|
||||
tooling you’ll use for the rest of your Rust career. In fact, you can get
|
||||
started with virtually all Rust projects you might find that you want to work
|
||||
on with the following commands:
|
||||
|
||||
```bash
|
||||
$ git clone someurl.com/someproject
|
||||
$ cd someproject
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
> Note: If you want to look at Cargo in more detail, check out the official
|
||||
[Cargo guide], which covers all of its features.
|
||||
|
||||
[Cargo guide]: http://doc.crates.io/guide.html
|
966
nostarch/chapter02.md
Normal file
966
nostarch/chapter02.md
Normal file
@ -0,0 +1,966 @@
|
||||
# Guessing Game
|
||||
|
||||
Let's jump into Rust with a hands-on 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.
|
||||
|
||||
## Set up
|
||||
|
||||
Let’s set up a new project. Go to your projects directory, and create a new project using Cargo.
|
||||
|
||||
```bash
|
||||
$ cd ~/projects
|
||||
$ cargo new guessing_game --bin
|
||||
$ cd guessing_game
|
||||
```
|
||||
|
||||
We pass the name of our project to `cargo new`, then the `--bin` flag, since
|
||||
we’re going to be making another binary like in Chapter 1.
|
||||
|
||||
Take a look at the generated `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "guessing_game"
|
||||
version = "0.1.0"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[dependencies]
|
||||
```
|
||||
|
||||
If the authors information that Cargo got from your environment is not correct,
|
||||
go ahead and fix that.
|
||||
|
||||
And as we saw in the last chapter, `cargo new` generates a ‘Hello, world!’ for
|
||||
us. Check out `src/main.rs`:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Let’s try compiling what Cargo gave us and running it in the same step, using the `cargo run` command:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (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 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. We’ll be writing all of our code in this
|
||||
file.
|
||||
|
||||
## 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`:
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
There’s a lot here! Let’s go over it, bit by bit.
|
||||
|
||||
```rust,ignore
|
||||
use std::io;
|
||||
```
|
||||
|
||||
We’ll 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
|
||||
by default into every program, [the ‘prelude’][prelude]. If it’s not in the
|
||||
prelude, you’ll have to `use` it directly. Using the `std::io` library gets
|
||||
you a number of useful `io`-related things, so that's what we've done here.
|
||||
|
||||
[prelude]: ../std/prelude/index.html
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
```
|
||||
|
||||
As you’ve 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.");
|
||||
```
|
||||
|
||||
We previously learned in Chapter 1 that `println!()` is a macro that
|
||||
prints a string to the screen.
|
||||
|
||||
### Variable Bindings
|
||||
|
||||
```rust,ignore
|
||||
let mut guess = String::new();
|
||||
```
|
||||
|
||||
Now we’re getting interesting! There’s a lot going on in this little line.
|
||||
The first thing to notice is that this is a let statement, which is
|
||||
used to create what are called ‘variable bindings’. Here's an example:
|
||||
|
||||
```rust,ignore
|
||||
let foo = bar;
|
||||
```
|
||||
|
||||
This will create a new binding named `foo`, and bind it to the value `bar`. In
|
||||
many languages, this is called a ‘variable’, but Rust’s variable bindings have
|
||||
a few tricks up their sleeves.
|
||||
|
||||
For example, they’re immutable by default. That’s why our example
|
||||
uses `mut`: it makes a binding mutable, rather than immutable.
|
||||
|
||||
```rust
|
||||
let foo = 5; // immutable.
|
||||
let mut bar = 5; // mutable
|
||||
```
|
||||
|
||||
Oh, and `//` will start a comment, until the end of the line. Rust ignores
|
||||
everything in comments.
|
||||
|
||||
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 it’s
|
||||
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 uses `::` because this is an ‘associated function’ of
|
||||
a particular type. That is to say, it’s 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`.
|
||||
You’ll find a `new()` function on many types, as it’s a common name for making
|
||||
a new value of some kind.
|
||||
|
||||
Let’s move forward:
|
||||
|
||||
```rust,ignore
|
||||
io::stdin().read_line(&mut guess)
|
||||
.expect("Failed to read line");
|
||||
```
|
||||
|
||||
Let’s go through this together bit-by-bit. The first line has two parts. Here’s
|
||||
the first:
|
||||
|
||||
```rust,ignore
|
||||
io::stdin()
|
||||
```
|
||||
|
||||
Remember how we `use`d `std::io` on the first line of the program? We’re now
|
||||
calling an associated function on it. If we didn’t `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. We’re also
|
||||
passing one argument to `read_line()`: `&mut guess`.
|
||||
|
||||
[read_line]: ../std/io/struct.Stdin.html#method.read_line
|
||||
|
||||
Remember how we bound `guess` above? We said it was mutable. However,
|
||||
`read_line` doesn’t take a `String` as an argument: it takes a `&mut String`.
|
||||
The `&` is the feature of Rust called a ‘reference’, which allows you to have
|
||||
multiple ways to access one piece of data in order to reduce copying.
|
||||
References are a complex feature, as one of Rust’s major selling points is how
|
||||
safe and easy it is to use references. We don’t need to know a lot of those
|
||||
details to finish our program right now, though; Chapter XX will cover them 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`.
|
||||
|
||||
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, that string needs to be mutable.
|
||||
|
||||
But we’re not quite done with this line of code, though. While it’s
|
||||
a single line of text, it’s only the first part of the single logical line of
|
||||
code. This is the second part of the line:
|
||||
|
||||
```rust,ignore
|
||||
.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
|
||||
written this code as:
|
||||
|
||||
```rust,ignore
|
||||
io::stdin().read_line(&mut guess).expect("failed to read line");
|
||||
```
|
||||
|
||||
But that gets hard to read. So we’ve split it up, two lines for two method
|
||||
calls.
|
||||
|
||||
### The `Result` Type
|
||||
|
||||
We already talked about `read_line()`, but what about `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 [`expect()` method][expect] that takes a value
|
||||
it’s called on, and if it isn’t a successful result, will cause our program to
|
||||
crash and display the message that we passed as an argument to `expect()`.
|
||||
|
||||
[expect]: ../std/result/enum.Result.html#method.expect
|
||||
|
||||
If we don't call this method, our program will compile, but we’ll 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 haven’t used the `Result` value. This warning comes from
|
||||
a special annotation that `io::Result` has. Rust is trying to tell you that you
|
||||
haven’t handled a possible error. The right way to suppress the error is to
|
||||
actually write error handling. Luckily, if we want to crash if there’s a
|
||||
problem, we can use `expect()`. If we can recover from the error somehow, we’d
|
||||
do something else, but we’ll save that for a future project.
|
||||
|
||||
### `println!()` Placeholders
|
||||
|
||||
There’s 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:
|
||||
think of `{}` as little crab pincers, holding a value in place. The first `{}`
|
||||
holds the first value 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:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
let y = 10;
|
||||
|
||||
println!("x and y: {} and {}", x, y);
|
||||
```
|
||||
|
||||
Which would print out "x and y: 5 and 10".
|
||||
|
||||
Anyway, back to our guessing game. 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.
|
||||
We’ve been building a ‘binary crate’, which is an executable. `rand` is a
|
||||
‘library crate’, which contains code that’s 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 this line at the bottom beneath the `[dependencies]` section header that
|
||||
Cargo created for you:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
|
||||
rand = "0.3.14"
|
||||
```
|
||||
|
||||
The `[dependencies]` section of `Cargo.toml` is like the `[package]` section:
|
||||
everything that follows the section heading is part of that section, until
|
||||
another section starts. Cargo uses the dependencies section to know what
|
||||
dependencies on external crates you have and what versions of those crates you
|
||||
require. In this case, we’ve specified the `rand` crate with the semantic
|
||||
version specifier `0.3.14`. Cargo understands [Semantic Versioning][semver], 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".
|
||||
|
||||
[semver]: http://semver.org
|
||||
|
||||
Now, without changing any of our code, let’s 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
|
||||
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
|
||||
```
|
||||
|
||||
You may see different versions (but they will be compatible, thanks to semver!)
|
||||
and the lines may be in a different order.
|
||||
|
||||
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 don’t have yet. In this case, while we only said we wanted to depend on
|
||||
`rand`, we’ve 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, we’ll get different output:
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
That’s 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. There’s
|
||||
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,
|
||||
we’ll only see one line:
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
|
||||
```
|
||||
|
||||
What happens when next week version `v0.3.15` of the `rand` crate comes out,
|
||||
with an important bugfix? While getting bugfixes is important, what if `0.3.15`
|
||||
contains a regression that breaks our code?
|
||||
|
||||
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
|
||||
project for the first time, Cargo figures out all of the versions 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 then use
|
||||
that specific version rather than do all the work of figuring out versions
|
||||
again. This lets you have a repeatable build automatically. In other words,
|
||||
we’ll stay at `0.3.14` until we explicitly upgrade, and so will anyone who we
|
||||
share our code with, thanks to the lock file.
|
||||
|
||||
What about when we _do_ want to use `v0.3.15`? Cargo has another command,
|
||||
`update`, which says ‘ignore the `Cargo.lock` file and figure out all the
|
||||
latest versions that fit what we’ve specified 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 we want to
|
||||
move to `0.4.x`, we’d have to update what is in the `Cargo.toml` file. When we
|
||||
do, the next time we `cargo build`, Cargo will update the index and re-evaluate
|
||||
our `rand` requirements.
|
||||
|
||||
There’s a lot more to say about [Cargo][doccargo] and [its
|
||||
ecosystem][doccratesio] that we will get into in Chapter XX, but for now,
|
||||
that’s 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.
|
||||
|
||||
[doccargo]: http://doc.crates.io
|
||||
[doccratesio]: http://doc.crates.io/crates-io.html
|
||||
|
||||
Let’s get on to actually _using_ `rand`. Here’s 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)
|
||||
.expect("failed to read line");
|
||||
|
||||
println!("You guessed: {}", guess);
|
||||
}
|
||||
```
|
||||
|
||||
The first thing we’ve done is change the first line. It now says `extern crate
|
||||
rand`. Because we declared `rand` in our `[dependencies]`, we can now put
|
||||
`extern crate` in our code to let Rust know we’ll be making use of that
|
||||
dependency. This also does the equivalent of a `use rand;` as well, so we can
|
||||
call anything in the `rand` crate by prefixing it with `rand::`.
|
||||
|
||||
Next, we added another `use` line: `use rand::Rng`. We’re 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 section in Chapter XX.
|
||||
|
||||
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 of execution
|
||||
we’re in. Because we put `use rand::Rng` above, the random number generator has
|
||||
a `gen_range()` method available. This method takes two numbers as arguments
|
||||
and generates a random number between them. It’s 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.
|
||||
|
||||
The second line prints out the secret number. This is useful while
|
||||
we’re developing our program to let us easily test it out, but we’ll be
|
||||
deleting it for the final version. It’s 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
|
||||
```
|
||||
|
||||
You should get different random numbers, and they should all be between 1 and
|
||||
100. Great job! Next up: comparing our guess to the secret number.
|
||||
|
||||
## Comparing guesses
|
||||
|
||||
Now that we’ve got user input, let’s compare our guess to the secret number.
|
||||
Here’s part of our next step. It won't quite compile yet though:
|
||||
|
||||
```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!"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A few new bits here. The first is another `use`. We bring a type called
|
||||
`std::cmp::Ordering` into scope. Then we add five new lines at the bottom that
|
||||
use that type:
|
||||
|
||||
```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. An arm is made up of a pattern and the
|
||||
code that we should execute if the pattern matches the value of the type. 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 it’s `Less`, we print `Too small!`, if it’s `Greater`, `Too big!`, and if
|
||||
`Equal`, `You win!`. `match` is really useful and is used often in Rust.
|
||||
|
||||
We did mention that this won’t quite compile yet, though. Let’s try it:
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
|
||||
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`.
|
||||
```
|
||||
|
||||
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`, so it doesn’t make us write out the type. 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 hasn’t mattered, and so Rust defaults to an `i32`. However, here, Rust
|
||||
doesn’t 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 so that we can compare it to the guess numerically. We
|
||||
can do that with two more lines. Here’s 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)
|
||||
.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!"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new two lines:
|
||||
|
||||
```rust,ignore
|
||||
let guess: u32 = guess.trim().parse()
|
||||
.expect("Please type a number!");
|
||||
```
|
||||
|
||||
Wait a minute, didn't we already have 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 a `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()
|
||||
```
|
||||
|
||||
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()`. 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 only 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 we’re going to annotate its type. `u32` is an
|
||||
unsigned, thirty-two bit integer. Rust has a number of built-in number
|
||||
types, but we’ve chosen `u32`. It’s a good default choice for a small
|
||||
positive number. You'll see the other number types in Chapter XX.
|
||||
|
||||
[parse]: ../std/primitive.str.html#method.parse
|
||||
|
||||
Just like `read_line()`, our call to `parse()` could cause an error. What if
|
||||
our string contained `A👍%`? There’d be no way to convert that to a number. As
|
||||
such, we’ll do the same thing we did with `read_line()`: use the `expect()`
|
||||
method to crash if there’s an error.
|
||||
|
||||
Let’s try our program out!
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (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 we even added spaces before our guess, and it still figured
|
||||
out that we guessed 76. Run the program a few times. Verify that guessing
|
||||
the secret number works, as well as guessing a number too small.
|
||||
|
||||
Now we’ve got most of the game working, but we can only make one guess. Let’s
|
||||
change that by adding loops!
|
||||
|
||||
## Looping
|
||||
|
||||
The `loop` keyword gives us an infinite loop. Let’s 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)
|
||||
.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!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And try it out. But wait, didn’t we just add an infinite loop? Yup. Remember
|
||||
our discussion about `parse()`? If we give a non-number answer, the program
|
||||
will crash and, therefore, quit. Observe:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (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!: 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)
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By adding the `break` line after the `You win!`, we’ll exit the loop when we
|
||||
win. Exiting the loop also means exiting the program, since the loop is the last
|
||||
thing in `main()`. We have another tweak to make: when someone inputs a
|
||||
non-number, we don’t want to quit, we 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)
|
||||
.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 `expect()` to a `match` statement. A `Result` is the
|
||||
return type of `parse()`. `Result` is an `enum` 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: in this case, the
|
||||
successfully parsed integer or an error type, respectively. When we `match` an
|
||||
`Ok(num)`, that pattern sets the name `num` to the value inside the `Ok` (the
|
||||
integer), and the code we run just returns that integer. In the `Err` case, we
|
||||
don’t care what kind of error it is, so we just use the catch-all `_` instead
|
||||
of a name. So for all errors, we run the code `continue`, which lets us move to
|
||||
the next iteration of the loop, effectively ignoring the errors.
|
||||
|
||||
Now we should be good! Let’s try it:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (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 can finish 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:
|
||||
|
||||
```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!
|
||||
|
||||
This project showed you a lot: `let`, `match`, methods, associated
|
||||
functions, using external crates, and more.
|
||||
|
||||
At this point, you have successfully built the Guessing Game! Congratulations!
|
1303
nostarch/chapter03.md
Normal file
1303
nostarch/chapter03.md
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
486
nostarch/chapter05.md
Normal file
486
nostarch/chapter05.md
Normal file
@ -0,0 +1,486 @@
|
||||
# Structs
|
||||
|
||||
A `struct`, short for "structure", gives us the ability to name and package
|
||||
together multiple related values that make up a meaningful group. If you come
|
||||
from an object-oriented language, a `struct` is like an object's data
|
||||
attributes. `struct` and `enum` (that we talked about in the last chapter) are
|
||||
the building blocks you can use in Rust to create new types in your program's
|
||||
domain in order to take full advantage of Rust's compile-time type checking.
|
||||
|
||||
Let’s write a program which calculates the distance between two points.
|
||||
We’ll start off with single variable bindings, and then refactor it to
|
||||
use `struct`s instead.
|
||||
|
||||
Let’s make a new project with Cargo:
|
||||
|
||||
```bash
|
||||
$ cargo new --bin points
|
||||
$ cd points
|
||||
```
|
||||
|
||||
Here’s a short program which calculates the distance between two points. Put
|
||||
it into your `src/main.rs`:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x1 = 0.0;
|
||||
let y1 = 5.0;
|
||||
|
||||
let x2 = 12.0;
|
||||
let y2 = 0.0;
|
||||
|
||||
let answer = distance(x1, y1, x2, y2);
|
||||
|
||||
println!("Point 1: ({}, {})", x1, y1);
|
||||
println!("Point 2: ({}, {})", x2, y2);
|
||||
println!("Distance: {}", answer);
|
||||
}
|
||||
|
||||
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
|
||||
let x_squared = f64::powi(x2 - x1, 2);
|
||||
let y_squared = f64::powi(y2 - y1, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
```
|
||||
|
||||
Let's try running this program with `cargo run`:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling points v0.1.0 (file:///projects/points)
|
||||
Running `target/debug/points`
|
||||
Point 1: (0, 5)
|
||||
Point 2: (12, 0)
|
||||
Distance: 13
|
||||
```
|
||||
|
||||
Let's take a quick look at `distance()` before we move forward. To find the
|
||||
distance between two points, we can use the Pythagorean Theorem. The theorem is
|
||||
named after Pythagoras, who was the first person to mathematically prove this
|
||||
formula. The details aren't that important; just know the theorem says that the
|
||||
formula for the distance between two points is equal to:
|
||||
|
||||
- squaring the distance between the points horizontally (the "x" direction)
|
||||
- squaring the distance between the points vertically (the "y" direction)
|
||||
- adding those together
|
||||
- and taking the square root of that.
|
||||
|
||||
So that's what we're implementing here.
|
||||
|
||||
```rust,ignore
|
||||
f64::powi(2.0, 3)
|
||||
```
|
||||
|
||||
The double colon (`::`) here is a namespace operator. We haven’t talked about
|
||||
modules and namespaces in depth yet, but you can think of the `powi()` function
|
||||
as being scoped inside of another name. In this case, the name is `f64`, the
|
||||
same as the type. The `powi()` function takes two arguments: the first is a
|
||||
number, and the second is the power that it raises that number to. In this
|
||||
case, the second number is an integer, hence the ‘i’ in its name. Similarly,
|
||||
`sqrt()` is a function under the `f64` module, which takes the square root of
|
||||
its argument.
|
||||
|
||||
## Why `struct`s?
|
||||
|
||||
Our little program is okay, but we can do better. The key to seeing this is in
|
||||
the signature of `distance()`:
|
||||
|
||||
```rust,ignore
|
||||
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
|
||||
```
|
||||
|
||||
The distance function is supposed to calculate the distance between two points.
|
||||
But our distance function calculates some distance between four numbers. The
|
||||
first two and last two arguments are related, but that’s not expressed anywhere
|
||||
in our program itself. It would be nicer if we had a way to group `(x1, y1)`
|
||||
and `(x2, y2)` together.
|
||||
|
||||
We’ve already discussed one way to do that: tuples. Here’s a version of our
|
||||
program which uses tuples:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let p1 = (0.0, 5.0);
|
||||
|
||||
let p2 = (12.0, 0.0);
|
||||
|
||||
let answer = distance(p1, p2);
|
||||
|
||||
println!("Point 1: {:?}", p1);
|
||||
println!("Point 2: {:?}", p2);
|
||||
println!("Distance: {}", answer);
|
||||
}
|
||||
|
||||
fn distance(p1: (f64, f64), p2: (f64, f64)) -> f64 {
|
||||
let x_squared = f64::powi(p2.0 - p1.0, 2);
|
||||
let y_squared = f64::powi(p2.1 - p1.1, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
```
|
||||
|
||||
This is a little better, for sure. Tuples let us add a little bit of structure.
|
||||
We’re now passing two arguments, so that’s more clear. But it’s also worse:
|
||||
tuples don’t give names to their elements, so our calculation has gotten more
|
||||
confusing:
|
||||
|
||||
```rust,ignore
|
||||
p2.0 - p1.0
|
||||
p2.1 - p1.1
|
||||
```
|
||||
|
||||
When writing this example, your authors almost got it wrong themselves! Distance
|
||||
is all about `x` and `y` points, but our code is talking about `0` and `1`.
|
||||
This isn’t great.
|
||||
|
||||
Enter `struct`s. We can transform our tuples into something with a name for the
|
||||
whole as well as names for the parts:
|
||||
|
||||
```rust,ignore
|
||||
let p1 = (0.0, 5.0);
|
||||
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
let p1 = Point { x: 0.0, y: 5.0 };
|
||||
```
|
||||
|
||||
Here we've defined a `struct` and given it the name `Point`. The parts inside
|
||||
`{}` are defining the _fields_ of the struct. We can have as many or as few of
|
||||
them as we'd like, and we give them a name and specify their type. Here we have
|
||||
two fields named `x` and `y`, and they both hold `f64`s.
|
||||
|
||||
We can access the field of a struct in the same way we access an element of
|
||||
a tuple, except we use its name:
|
||||
|
||||
```rust,ignore
|
||||
let p1 = (0.0, 5.0);
|
||||
let x = p1.0;
|
||||
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
let p1 = Point { x: 0.0, y: 5.0 };
|
||||
let x = p1.x;
|
||||
```
|
||||
|
||||
Let’s convert our program to use our `Point` `struct`. Here’s what it looks
|
||||
like now:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p1 = Point { x: 0.0, y: 5.0};
|
||||
|
||||
let p2 = Point { x: 12.0, y: 0.0};
|
||||
|
||||
let answer = distance(p1, p2);
|
||||
|
||||
println!("Point 1: {:?}", p1);
|
||||
println!("Point 2: {:?}", p2);
|
||||
println!("Distance: {}", answer);
|
||||
}
|
||||
|
||||
fn distance(p1: Point, p2: Point) -> f64 {
|
||||
let x_squared = f64::powi(p2.x - p1.x, 2);
|
||||
let y_squared = f64::powi(p2.y - p1.y, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
```
|
||||
|
||||
Our function signature for `distance()` now says exactly what we mean: it
|
||||
calculates the distance between two `Point`s. And rather than `0` and `1`,
|
||||
we’ve got back our `x` and `y`. This is a win for clarity.
|
||||
|
||||
## Derived Traits
|
||||
|
||||
There’s one other thing that’s a bit strange here, this stuff above the
|
||||
`struct` declaration:
|
||||
|
||||
```rust,ignore
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point {
|
||||
```
|
||||
|
||||
This is an annotation that tells the compiler our struct should get some
|
||||
default behavior for the `Debug`, `Copy`, and `Clone` traits. We talked about
|
||||
marking that types can be `Copy` and `Clone`-able in Chapter XX when we
|
||||
discussed ownership. `Debug` is the trait that enables us to print out our
|
||||
struct so that we can see its value while we are debugging our code.
|
||||
|
||||
So far, we’ve been printing values using `{}` in a `println!` macro. If we try
|
||||
that with a struct, however, by default, we'll get an error. Say we have the
|
||||
following program:
|
||||
|
||||
```rust,ignore
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p1 = Point { x: 0.0, y: 5.0};
|
||||
println!("Point 1: {}", p1);
|
||||
}
|
||||
```
|
||||
|
||||
This code tries to print the `p1` point directly, which may seem innocuous. But
|
||||
running it produces the following output:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling points v0.1.0 (file:///projects/points)
|
||||
error: the trait bound `Point: std::fmt::Display` is not satisfied [--explain E0277]
|
||||
--> src/main.rs:8:29
|
||||
8 |> println!("Point 1: {}", p1);
|
||||
|> ^^
|
||||
<std macros>:2:27: 2:58: note: in this expansion of format_args!
|
||||
<std macros>:3:1: 3:54: note: in this expansion of print! (defined in <std macros>)
|
||||
src/main.rs:8:5: 8:33: note: in this expansion of println! (defined in <std macros>)
|
||||
note: `Point` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
|
||||
note: required by `std::fmt::Display::fmt`
|
||||
```
|
||||
|
||||
Whew! The core of the error is this part: *the trait bound `Point:
|
||||
std::fmt::Display` is not satisfied*. `println!` can do many kinds of
|
||||
formatting. By default, `{}` implements a kind of formatting known as
|
||||
`Display`: output intended for direct end-user consumption. The primitive types
|
||||
we’ve seen implement `Display`, as there’s only one way you’d show a `1` to a
|
||||
user. But with structs, the output is less clear. Do you want commas or not?
|
||||
What about the `{}`s? Should all the fields be shown?
|
||||
|
||||
More complex types in the standard library and that are defined by the
|
||||
programmer do not automatically implement `Display` formatting. Standard
|
||||
library types implement `Debug` formatting, which is intended for the
|
||||
programmer to see. The `#[derive(Debug)]` annotation lets us use a default
|
||||
implementation of `Debug` formatting to easily get this ability for types we've
|
||||
defined. To ask `println!` to use `Debug` formatting with our `Point`, we add
|
||||
the annotation to derive the trait and include `:?` in the print string, like
|
||||
this:
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p1 = Point { x: 0.0, y: 5.0};
|
||||
println!("Point 1: {:?}", p1);
|
||||
}
|
||||
```
|
||||
|
||||
If you run this, it should print the values of each field in the `Point` struct
|
||||
as desired:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling points v0.1.0 (file:///projects/points)
|
||||
Running `target/debug/points`
|
||||
Point 1: Point { x: 0, y: 5 }
|
||||
```
|
||||
|
||||
You’ll see this repeated later with other types. We’ll cover traits fully in
|
||||
Chapter XX.
|
||||
|
||||
## Method Syntax
|
||||
|
||||
In the last section on ownership, we made several references to ‘methods’.
|
||||
Methods look like this:
|
||||
|
||||
```rust
|
||||
let s1 = "hello";
|
||||
|
||||
// call a method on s1
|
||||
let s2 = s1.clone();
|
||||
|
||||
println!("{}", s1);
|
||||
```
|
||||
|
||||
The call to `clone()` is attached to `s1` with a dot. This is called ‘method
|
||||
syntax’, and it’s a way to call certain functions with a different style.
|
||||
|
||||
Why have two ways to call functions? We’ll talk about some deeper reasons
|
||||
related to ownership in a moment, but one big reason is that methods look nicer
|
||||
when chained together:
|
||||
|
||||
```rust,ignore
|
||||
// with functions
|
||||
h(g(f(x)));
|
||||
|
||||
// with methods
|
||||
x.f().g().h();
|
||||
```
|
||||
|
||||
The nested-functions version reads in reverse: the program executes `f()`, then
|
||||
`g()`, then `h()`, but we read it left-to-right as `h()`, then `g()`, then
|
||||
`f()`. The method syntax is executed in the same order as we would read it.
|
||||
|
||||
Before we get into the details, let’s talk about how to define your own
|
||||
methods.
|
||||
|
||||
### Defining methods
|
||||
|
||||
We can define methods with the `impl` keyword. `impl` is short for
|
||||
‘implementation’. Doing so looks like this:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
fn distance(&self, other: &Point) -> f64 {
|
||||
let x_squared = f64::powi(other.x - self.x, 2);
|
||||
let y_squared = f64::powi(other.y - self.y, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
}
|
||||
|
||||
let p1 = Point { x: 0.0, y: 0.0 };
|
||||
let p2 = Point { x: 5.0, y: 6.5 };
|
||||
|
||||
assert_eq!(8.200609733428363, p1.distance(&p2));
|
||||
```
|
||||
|
||||
Let’s break this down. First, we have our `Point` struct from earlier in the
|
||||
chapter. Next comes our first use of the `impl` keyword:
|
||||
|
||||
```rust,ignore
|
||||
impl Point {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Everything we put inside of the curly braces will be methods implemented on
|
||||
`Point`. Next is our definition:
|
||||
|
||||
```rust,ignore
|
||||
fn distance(&self, other: &Point) -> f64 {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Other than this, the rest of the example is familiar: an implementation of
|
||||
`distance()` and using the method to find an answer.
|
||||
|
||||
Our definition of `distance()` here as a method looks very similar to our
|
||||
previous definition of `distance()` as a function, but with two differences.
|
||||
Here's the `distance()` function again:
|
||||
|
||||
```rust,ignore
|
||||
fn distance(p1: Point, p2: Point) -> f64 {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The first difference is in the first argument. Instead of a name and a type, we
|
||||
have written `&self`. This is what distinguishes a method from a function:
|
||||
using `self` inside of an `impl` block means we have a method. Because we
|
||||
already know that we are implementing this method on `Point` because of the
|
||||
surrounding `impl Point` block, we don’t need to write the type of `self` out.
|
||||
|
||||
Note that we have written `&self`, not just `self`. This is because we want to
|
||||
take a reference to our argument's value rather than taking ownership of it. In
|
||||
other words, these two forms are the same:
|
||||
|
||||
```rust,ignore
|
||||
fn foo(self: &Point)
|
||||
fn foo(&self)
|
||||
```
|
||||
|
||||
Just like any other parameter, you can take `self` in three forms. Here’s the
|
||||
list, with the most common form first:
|
||||
|
||||
```rust,ignore
|
||||
fn foo(&self) // take self by reference
|
||||
fn foo(&mut self) // take self by mutable reference
|
||||
fn foo(self) // take self by ownership
|
||||
```
|
||||
|
||||
In this case, we only need a reference. We don’t need to mutate either `Point`
|
||||
to get the distance between them, so we won't take a mutable reference to the
|
||||
`Point` that we call the method on. Methods that take ownership of `self` are
|
||||
rarely used. An example of a time to do that would be if we wanted to have a
|
||||
method that would transform `self` into something else and prevent other code
|
||||
from using the value of `self` after the transformation happens.
|
||||
|
||||
#### Methods and automatic referencing
|
||||
|
||||
We’ve left out an important detail. It’s in this line of the example:
|
||||
|
||||
```rust,ignore
|
||||
assert_eq!(8.200609733428363, p1.distance(&p2));
|
||||
```
|
||||
|
||||
When we defined `distance()`, we took both `self` and the other argument by
|
||||
reference. Yet, we needed a `&` for `p2` but not `p1`. What gives?
|
||||
|
||||
This feature is called ‘automatic referencing’, and calling methods is one
|
||||
of the few places in Rust that has behavior like this. Here’s how it works:
|
||||
when you call a method with `self.(`, Rust will automatically add in `&`s
|
||||
or `&mut`s to match the signature. In other words, these are the same:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point {
|
||||
# x: f64,
|
||||
# y: f64,
|
||||
# }
|
||||
#
|
||||
# impl Point {
|
||||
# fn distance(&self, other: &Point) -> f64 {
|
||||
# let x_squared = f64::powi(other.x - self.x, 2);
|
||||
# let y_squared = f64::powi(other.y - self.y, 2);
|
||||
#
|
||||
# f64::sqrt(x_squared + y_squared)
|
||||
# }
|
||||
# }
|
||||
# let p1 = Point { x: 0.0, y: 0.0 };
|
||||
# let p2 = Point { x: 5.0, y: 6.5 };
|
||||
p1.distance(&p2);
|
||||
(&p1).distance(&p2);
|
||||
```
|
||||
|
||||
The first one looks much, much cleaner. Here’s another example:
|
||||
|
||||
```rust
|
||||
let mut s = String::from("Hello,");
|
||||
|
||||
s.push_str(" world!");
|
||||
|
||||
// The above is the same as:
|
||||
// (&mut s).push_str(" world!");
|
||||
|
||||
assert_eq!("Hello, world!", s);
|
||||
```
|
||||
|
||||
Because [`push_str()`] has the following signature:
|
||||
|
||||
```rust,ignore
|
||||
fn push_str(&mut self, string: &str) {
|
||||
```
|
||||
|
||||
[`push_str()`]: http://doc.rust-lang.org/collections/string/struct.String.html#method.push_str
|
||||
|
||||
This automatic referencing behavior works because methods have a clear receiver
|
||||
— the type of `self` — and in most cases it’s clear given the receiver and name
|
||||
of a method whether the method is just reading (so needs `&self`), mutating (so
|
||||
`&mut self`), or consuming (so `self`). The fact that Rust makes borrowing
|
||||
implicit for method receivers is a big part of making ownership ergonomic in
|
||||
practice.
|
601
nostarch/chapter06.md
Normal file
601
nostarch/chapter06.md
Normal file
@ -0,0 +1,601 @@
|
||||
# Enums
|
||||
|
||||
Next, let’s look at *enumerations*, which allow you to define a type by
|
||||
enumerating its possible values. Commonly called "enums", these unlock a lot of
|
||||
power in Rust when combined with pattern matching. Enums are a feature that are
|
||||
in many languages, but what they can do is different per-language. Rust’s enums
|
||||
are most similar to "algebraic data types" in functional languages like F#,
|
||||
OCaml, or Haskell.
|
||||
|
||||
Here’s an example of an enum definition:
|
||||
|
||||
```rust
|
||||
enum IpAddrKind {
|
||||
V4,
|
||||
V6,
|
||||
}
|
||||
```
|
||||
|
||||
This enum represents the kind of an IP address. There are two major standards
|
||||
used for IP addresses: version four and version six. Any IP address can be
|
||||
either a version four address or a version six address, but it cannot be both
|
||||
kinds at the same time. This is where enums get their name: they allow us to
|
||||
enumerate all of the possible kinds that our value can have.
|
||||
|
||||
We can create values of `IpAddrKind` like this:
|
||||
|
||||
```rust
|
||||
# enum IpAddrKind {
|
||||
# V4,
|
||||
# V6,
|
||||
# }
|
||||
#
|
||||
let four = IpAddrKind::V4;
|
||||
let six = IpAddrKind::V6;
|
||||
```
|
||||
|
||||
Note that the variants of the enum are namespaced under its name, and we use
|
||||
the double colon to separate the two.
|
||||
|
||||
Enums have more tricks up their sleeves, however. Thinking more about our IP
|
||||
address type, we don’t have a way to store the actual data of the IP address;
|
||||
we only know what kind it is. Given that you just learned about structs, you
|
||||
might tackle this problem like this:
|
||||
|
||||
```rust
|
||||
enum IpAddrKind {
|
||||
V4,
|
||||
V6,
|
||||
}
|
||||
|
||||
struct IpAddr {
|
||||
kind: IpAddrKind,
|
||||
address: String,
|
||||
}
|
||||
|
||||
let home = IpAddr {
|
||||
kind: IpAddrKind::V4,
|
||||
address: String::from("127.0.0.1"),
|
||||
};
|
||||
|
||||
let loopback = IpAddr {
|
||||
kind: IpAddrKind::V6,
|
||||
address: String::from("::1"),
|
||||
};
|
||||
```
|
||||
|
||||
We’ve used a struct to bundle the two values together: now we keep the kind
|
||||
with the value itself. We can represent the same thing in a different way with
|
||||
just an enum:
|
||||
|
||||
```rust
|
||||
enum IpAddr {
|
||||
V4(String),
|
||||
V6(String),
|
||||
}
|
||||
|
||||
let home = IpAddr::V4(String::from("127.0.0.1"));
|
||||
|
||||
let loopback = IpAddr::V6(String::from("::1"));
|
||||
```
|
||||
|
||||
We can attach data to each variant of the enum directly. No need for an extra
|
||||
struct. But beyond that, this approach is better than using a struct alongside
|
||||
our enum because we can attach different kinds of data to each variant.
|
||||
Imagine that instead of a `String`, we would prefer to store a `V4` as its four
|
||||
individual components while leaving the `V6` variant as a `String`. With our
|
||||
struct, we’d be stuck. But enums deal with this case with ease:
|
||||
|
||||
```rust
|
||||
enum IpAddr {
|
||||
V4(u8, u8, u8, u8),
|
||||
V6(String),
|
||||
}
|
||||
|
||||
let home = IpAddr::V4(127, 0, 0, 1);
|
||||
|
||||
let loopback = IpAddr::V6(String::from("::1"));
|
||||
```
|
||||
|
||||
You can put any kind of data inside of an enum variant, including another enum!
|
||||
The `IpAddr` enum is [in the standard library][IpAddr], but it embeds two
|
||||
different structs inside of its variants:
|
||||
|
||||
```rust
|
||||
struct Ipv4Addr {
|
||||
// details elided
|
||||
}
|
||||
|
||||
struct Ipv6Addr {
|
||||
// details elided
|
||||
}
|
||||
|
||||
enum IpAddr {
|
||||
V4(Ipv4Addr),
|
||||
V6(Ipv6Addr),
|
||||
}
|
||||
```
|
||||
|
||||
[IpAddr]: http://doc.rust-lang.org/std/net/enum.IpAddr.html
|
||||
|
||||
Here’s an enum with a variety of types embedded in its variants:
|
||||
|
||||
```rust
|
||||
enum Message {
|
||||
Quit,
|
||||
Move { x: i32, y: i32 },
|
||||
Write(String),
|
||||
ChangeColor(i32, i32, i32),
|
||||
}
|
||||
```
|
||||
|
||||
* `Quit` has no data associated with it at all.
|
||||
* `Move` includes an anonymous struct inside of it.
|
||||
* `Write` includes a single `String`.
|
||||
* `ChangeColor` includes three `i32`s.
|
||||
|
||||
This might seem overwhelming, but another way to look at the different enum
|
||||
possibilities is that they are just like different kinds of struct definitions
|
||||
that you already know, except without the `struct` keyword and they are grouped
|
||||
together under the `Message` type. These structs could hold the same data that
|
||||
these enum variants hold:
|
||||
|
||||
```
|
||||
struct QuitMessage; // unit struct
|
||||
struct MoveMessage {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
struct WriteMessage(String); // tuple struct
|
||||
struct ChangeColorMessage(i32, i32, i32); // tuple struct
|
||||
```
|
||||
|
||||
Let's look at another enum in the standard library that is very common and
|
||||
useful: `Option`.
|
||||
|
||||
## Option
|
||||
|
||||
Now that we have had an introduction to enums, let's combine them with a
|
||||
feature that we talked a little bit about in the previous chapter: generics.
|
||||
|
||||
Programming language design is often thought of as which features you include,
|
||||
but it's also about which features you leave out. Rust does not have a feature
|
||||
that is in many other languages: 'null'. In languages with this feature,
|
||||
variables can have two states: null or not-null.
|
||||
|
||||
The inventor of this concept has this to say:
|
||||
|
||||
> I call it my billion-dollar mistake. At that time, I was designing the first
|
||||
> comprehensive type system for references in an object-oriented language. My
|
||||
> goal was to ensure that all use of references should be absolutely safe, with
|
||||
> checking performed automatically by the compiler. But I couldn't resist the
|
||||
> temptation to put in a null reference, simply because it was so easy to
|
||||
> implement. This has led to innumerable errors, vulnerabilities, and system
|
||||
> crashes, which have probably caused a billion dollars of pain and damage in
|
||||
> the last forty years.
|
||||
>
|
||||
> - Tony Hoare "Null References: The Billion Dollar Mistake"
|
||||
|
||||
The problem with null values is twofold: first, a value can be null or not, at
|
||||
any time. The second is that if you try to use a value that's null, you'll get
|
||||
an error of some kind, depending on the language. Because this property is
|
||||
pervasive, it's extremely easy to make this kind of error.
|
||||
|
||||
Even with these problems, the concept that null is trying to express is still a
|
||||
useful one: this is a value which is currently invalid or not present for some
|
||||
reason. The problem isn't with the concept itself, but with the particular
|
||||
implementation. As such, Rust does not have the concept of null, but we do have
|
||||
an enum which can encode the concept of a value being present or not present. We
|
||||
call this enum `Option<T>`, and it looks like this:
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None,
|
||||
}
|
||||
```
|
||||
|
||||
This enum is [provided by the standard library][option], and is so useful that
|
||||
it's even in the prelude; you don't need to import it explicitly. Furthermore,
|
||||
so are its variants: you can say `Some` and `None` directly, without prefixing
|
||||
them with `Option::`.
|
||||
|
||||
[option]: ../std/option/enum.Option.html
|
||||
|
||||
Here's an example of using `Option<T>`:
|
||||
|
||||
```rust
|
||||
let some_number = Some(5);
|
||||
let some_string = Some("a string");
|
||||
|
||||
// If we only say None, we need to tell Rust what type of Option<T> we have.
|
||||
let absent_number: Option<i32> = None;
|
||||
```
|
||||
|
||||
Let's dig in. First, you'll notice that we used the `<T>` syntax when defining
|
||||
`Option<T>`: it's a generic enum. `Option<T>` has two variants: `Some`, which
|
||||
contains a `T`, and `None`, which has no data associated with it. In some
|
||||
sense, `None` means 'null', and `Some` means 'not null'. So why is this any
|
||||
better than null?
|
||||
|
||||
In short, because `Option<T>` and `T` are different types. That's a bit too
|
||||
short though. Here's an example:
|
||||
|
||||
```rust,ignore
|
||||
let x: i8 = 5;
|
||||
let y: Option<i8> = Some(5);
|
||||
|
||||
let sum = x + y;
|
||||
```
|
||||
|
||||
This will not compile. We get an error message like this:
|
||||
|
||||
```text
|
||||
error: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not
|
||||
satisfied [E0277]
|
||||
|
||||
let sum = x + y;
|
||||
^~~~~
|
||||
```
|
||||
|
||||
Intense! What this error message is trying to say is that Rust does not
|
||||
understand how to add an `Option<T>` and a `T`. They're different types! This
|
||||
shows one of the big advantages of an `Option<T>`: if you have a value that
|
||||
may or may not exist, you have to deal with that fact before you can assume it
|
||||
exists. In other words, you have to convert an `Option<T>` to a `T` before you
|
||||
can do `T` stuff with it. This helps catch one of the most common issues with
|
||||
null, generally: assuming that something isn't null when it actually is.
|
||||
|
||||
This is pretty powerful: in order to have a value that can possibly be null,
|
||||
you have to explicitly opt in by making the type of that value an `Option<T>`.
|
||||
Then, when you use that value, you are required to explicitly handle the case
|
||||
when the value is null. Everywhere that a value has a type that isn't an
|
||||
`Option<T>`, you *can* safely assume that the value isn't null. This was a
|
||||
deliberate design decision for Rust to limit null's pervasiveness and increase
|
||||
the safety of Rust code.
|
||||
|
||||
So, how _do_ you get a `T` from an `Option<T>`? The `Option<T>` enum has a
|
||||
large number of methods that you can check out in [its documentation], and
|
||||
becoming familiar with them will be extremely useful in your journey with Rust.
|
||||
|
||||
[its documentation]: ../std/option/enum.Option.html
|
||||
|
||||
But we want a deeper understanding than that. If we didn't have those methods
|
||||
defined for us already, what would we do? And more generally, how do we get
|
||||
the inner values out of any enum variant? We need a new feature: `match`.
|
||||
|
||||
## Match
|
||||
|
||||
Rust has an extremely powerful control-flow operator: `match`. It allows us to
|
||||
compare a value against a series of patterns and then execute code based on
|
||||
how they compare.
|
||||
|
||||
Think of a `match` expression kind of like a coin sorting machine. Coins slide
|
||||
down a track that has variously sized holes along it, and each coin falls
|
||||
through the first hole it encounters that it fits into. In the same way, values
|
||||
go through each pattern in a `match`, and for the first pattern that the value
|
||||
"fits", the value will fall into the associated code block to be used during
|
||||
execution.
|
||||
|
||||
Since we're already talking about coins, let's use them for an example using
|
||||
`match`! We can write a function that can take an unknown American coin and, in
|
||||
a similar way as the coin counting machine, determine which coin it is and
|
||||
return its value in cents:
|
||||
|
||||
```rust
|
||||
enum Coin {
|
||||
Penny,
|
||||
Nickel,
|
||||
Dime,
|
||||
Quarter,
|
||||
}
|
||||
|
||||
fn value_in_cents(coin: Coin) -> i32 {
|
||||
match coin {
|
||||
Coin::Penny => 1,
|
||||
Coin::Nickel => 5,
|
||||
Coin::Dime => 10,
|
||||
Coin::Quarter => 25,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's break down the `match`! At a high-level, using `match` looks like this:
|
||||
|
||||
```text
|
||||
match expression {
|
||||
pattern => code,
|
||||
}
|
||||
```
|
||||
|
||||
First, we have the `match` keyword. Next, we have an expression. This feels
|
||||
very similar to an expression used with `if`, but there's a big difference:
|
||||
with `if`, the condition needs to return a boolean value. Here, it can be any
|
||||
type.
|
||||
|
||||
Next, we have a "match arm". That's the part that looks like `pattern =>
|
||||
code,`. We can have as many arms as we need to: our `match` above has four
|
||||
arms. An arm has two parts: a pattern and some code. When the `match`
|
||||
expression executes, it compares the resulting value against the pattern of
|
||||
each arm, in order. If a pattern matches the value, the code associated
|
||||
with that pattern is executed. If that pattern doesn't match the value,
|
||||
execution continues to the next arm, much like a coin sorting machine.
|
||||
|
||||
The code associated with each arm is an expression, and the resulting value of
|
||||
the code with the matching arm that gets executed is the value that gets
|
||||
returned for the entire `match` expression.
|
||||
|
||||
Curly braces typically aren't used if the match arm code is short, as it is in
|
||||
the above example where each arm just returns a value. If we wanted to run
|
||||
multiple lines of code in a match arm, we can use curly braces. This code would
|
||||
print out "Lucky penny!" every time the method was called with a `Coin::Penny`,
|
||||
but would still return the last value of the block, `1`:
|
||||
|
||||
```rust
|
||||
# enum Coin {
|
||||
# Penny,
|
||||
# Nickel,
|
||||
# Dime,
|
||||
# Quarter,
|
||||
# }
|
||||
#
|
||||
fn value_in_cents(coin: Coin) -> i32 {
|
||||
match coin {
|
||||
Coin::Penny => {
|
||||
println!("Lucky penny!");
|
||||
1
|
||||
},
|
||||
Coin::Nickel => 5,
|
||||
Coin::Dime => 10,
|
||||
Coin::Quarter => 25,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Another useful feature of match arms is that they can create bindings to parts
|
||||
of the values that match the pattern. From 1999 through 2008, the U.S. printed
|
||||
quarters with different designs for each of the 50 states on one side. The other
|
||||
coins did not get state designs, so only quarters have this extra attribute. We
|
||||
can add this information to our `enum` by changing the `Quarter` variant to have
|
||||
a `State` value:
|
||||
|
||||
```rust
|
||||
enum UsState {
|
||||
Alabama,
|
||||
Alaska,
|
||||
// ... etc
|
||||
}
|
||||
|
||||
enum Coin {
|
||||
Penny,
|
||||
Nickel,
|
||||
Dime,
|
||||
Quarter(UsState),
|
||||
}
|
||||
```
|
||||
|
||||
Let's imagine that a friend of ours is trying to collect all 50 state quarters.
|
||||
While we sort our loose change by coin type in order to count it, we're going
|
||||
to call out the name of the state so that if it's one our friend doesn't have
|
||||
yet, they can add it to their collection.
|
||||
|
||||
In the match statement to do this, the quarter case now has a binding, `state`,
|
||||
that contains the value of the state of that quarter. The binding will only get
|
||||
created if the coin matches the `Quarter` pattern. Then we can use the binding
|
||||
in the code for that arm:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug)]
|
||||
# enum UsState {
|
||||
# Alabama,
|
||||
# Alaska,
|
||||
# }
|
||||
#
|
||||
# enum Coin {
|
||||
# Penny,
|
||||
# Nickel,
|
||||
# Dime,
|
||||
# Quarter(UsState),
|
||||
# }
|
||||
#
|
||||
fn value_in_cents(coin: Coin) -> i32 {
|
||||
match coin {
|
||||
Coin::Penny => 1,
|
||||
Coin::Nickel => 5,
|
||||
Coin::Dime => 10,
|
||||
Coin::Quarter(state) => {
|
||||
println!("State quarter from {:?}!", state);
|
||||
25
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we were to call `value_in_cents(Coin::Quarter(UsState::Alaska))`, `coin` will
|
||||
be `Coin::Quarter(UsState::Alaska)`. When we compare that value with each of the
|
||||
match arms, none of them match until we reach `Coin::Quarter(state)`. At that
|
||||
point, the binding for `state` will be the value `UsState::Alaska`. We can then
|
||||
use that binding in the `println!`, thus getting the inner state value out of
|
||||
the `Coin` enum variant for `Quarter`.
|
||||
|
||||
Remember the `Option<T>` type from the previous section, and that we wanted to
|
||||
be able to get the inner `T` value out of the `Some` case? This will be very
|
||||
similar! Instead of coins, we will be comparing to other patterns, but the way
|
||||
that the `match` expression works remains the same as a coin sorting machine in
|
||||
the way that we look for the first pattern that fits the value.
|
||||
|
||||
Let's say that we want to write a function that takes an `Option<i32>`, and
|
||||
if there's a value inside, add one to it. If there isn't a value inside, we
|
||||
want to return the `None` value and not attempt to add.
|
||||
|
||||
This function is very easy to write, thanks to `match`. It looks like this:
|
||||
|
||||
```rust
|
||||
fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
match x {
|
||||
None => None,
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
}
|
||||
|
||||
let five = Some(5);
|
||||
let six = plus_one(five);
|
||||
let none = plus_one(None);
|
||||
```
|
||||
|
||||
Let's examine the first execution of `plus_one()` in more detail. In the above
|
||||
example, `x` will be `Some(5)`. Let's compare that against each arm:
|
||||
|
||||
```text
|
||||
None => None,
|
||||
```
|
||||
|
||||
Does `Some(5)` match `None`? No, it's the wrong variant. So let's continue.
|
||||
|
||||
```text
|
||||
Some(i) => Some(i + 1),
|
||||
```
|
||||
|
||||
Does `Some(5)` match `Some(i)`? Why yes it does! We have the same variant. The
|
||||
`i` binds to the value inside of the `Some`, so `i` has the value `5`. Then we
|
||||
execute the code in that match arm: take `i`, which is `5`, add one to it, and
|
||||
create a new `Some` value with our total inside.
|
||||
|
||||
Now let's consider the second call of `plus_one()`. In this case, `x` is
|
||||
`None`. We enter the `match`, and compare to the first arm:
|
||||
|
||||
```text
|
||||
None => None,
|
||||
```
|
||||
|
||||
Does `None` match `None`? Yup! There's no value to add to. So we stop and
|
||||
return the `None` value that is on the right side of the `=>`. We don't
|
||||
check any other arms since we found one that matched.
|
||||
|
||||
Combining `match` and enums together is extremely powerful. You'll see this
|
||||
pattern a lot in Rust code: `match` against an enum, bind to the data
|
||||
inside, and then execute code based on it. It's a bit tricky at first, but
|
||||
once you get used to it, you'll wish you had it in languages that don't support
|
||||
it. It's consistently a user favorite.
|
||||
|
||||
### Matches are exhaustive
|
||||
|
||||
There's one other aspect of `match` we didn't talk about. Consider this version
|
||||
of `plus_one()`:
|
||||
|
||||
```rust,ignore
|
||||
fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
match x {
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A bug! We didn't handle the `None` case. Luckily, it's a bug Rust knows how to
|
||||
catch. If we try to compile this code, we'll get an error:
|
||||
|
||||
```text
|
||||
error: non-exhaustive patterns: `None` not covered [E0004]
|
||||
match x {
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
```
|
||||
|
||||
Rust knows that we did not cover every possible option, and even knows which
|
||||
pattern we forgot! This is referred to as being "exhaustive": we must exhaust
|
||||
every last option possible in order to be valid. Especially in the case of
|
||||
`Option<T>`, when Rust prevents us from forgetting to explicitly handle the
|
||||
`None` case, it protects us from assuming that we have a value when we might
|
||||
have null and thus making the billion-dollar mistake we discussed in the
|
||||
previous section.
|
||||
|
||||
### The _ placeholder
|
||||
|
||||
What if we don't care about all of the possible values, though? Especially when
|
||||
there are a lot of possible values for a type: a `u8` can have valid values of
|
||||
zero through 255-- if we only care about 1, 3, 5, and 7, does this mean we must
|
||||
list out 0, 2, 4, 6, 8, 9, all the way up through 255? Thankfully, no! We can
|
||||
use a special pattern, `_`:
|
||||
|
||||
```rust
|
||||
let some_u8_value = 0u8;
|
||||
match some_u8_value {
|
||||
1 => println!("one"),
|
||||
3 => println!("three"),
|
||||
5 => println!("five"),
|
||||
7 => println!("seven"),
|
||||
_ => (),
|
||||
}
|
||||
```
|
||||
|
||||
The `_` pattern will match all the other cases, and `()` will do nothing, it's
|
||||
the unit value. This way, we don't have to list individual match arms for all
|
||||
the other possible values in order to say that we want to do nothing for all of
|
||||
those-- the `_` is a placeholder for any value.
|
||||
|
||||
## if let
|
||||
|
||||
There's one more advanced control flow structure we haven't discussed: `if
|
||||
let`. Imagine we're in a situation like this:
|
||||
|
||||
```rust
|
||||
# let some_option = Some(5);
|
||||
match some_option {
|
||||
Some(x) => {
|
||||
// do something with x
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
```
|
||||
|
||||
We care about the `Some` case, but don't want to do anything with the `None`
|
||||
case. With an `Option`, this isn't _too_ bad, but with a more complex enum,
|
||||
adding `_ => {}` after processing just one variant doesn't feel great. We have
|
||||
this boilerplate arm and an extra level of indentation (the code that
|
||||
does something with `x` is indented twice, rather than just once). We really want
|
||||
a construct that says "Do something with this one case; I don't care about the
|
||||
others."
|
||||
|
||||
Enter `if let`:
|
||||
|
||||
```rust
|
||||
# let some_option = Some(5);
|
||||
if let Some(x) = some_option {
|
||||
// do something with x
|
||||
}
|
||||
```
|
||||
|
||||
`if let` takes a pattern and an expression, separated by an `=`. It works
|
||||
exactly like a `match`, where the expression is given to the `match` and the
|
||||
pattern is its first arm. In other words, you can think of `if let` as syntax
|
||||
sugar:
|
||||
|
||||
```rust,ignore
|
||||
if let pattern = expression {
|
||||
body
|
||||
}
|
||||
|
||||
match expression {
|
||||
pattern => body,
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
And in fact, we can include an `else` and it becomes the body of the `_`
|
||||
case:
|
||||
|
||||
```rust,ignore
|
||||
if let pattern = expression {
|
||||
body
|
||||
} else {
|
||||
else_body
|
||||
}
|
||||
|
||||
match expression {
|
||||
pattern => body,
|
||||
_ => else_body,
|
||||
}
|
||||
```
|
||||
|
||||
In other words, it's the high-level construct we were originally looking for:
|
||||
do something special with only one pattern.
|
2332
nostarch/chapter3.md
2332
nostarch/chapter3.md
File diff suppressed because it is too large
Load Diff
@ -1,697 +0,0 @@
|
||||
# Structs
|
||||
|
||||
So far, all of the data types we’ve seen allow us to have a single value
|
||||
at a time. `struct`s give us the ability to package up multiple values and
|
||||
keep them in one related structure.
|
||||
|
||||
Let’s write a program which calculates the distance between two points.
|
||||
We’ll start off with single variable bindings, and then refactor it to
|
||||
use `struct`s instead.
|
||||
|
||||
Let’s make a new project with Cargo:
|
||||
|
||||
```bash
|
||||
$ cargo new --bin points
|
||||
$ cd points
|
||||
```
|
||||
|
||||
Here’s a short program which calculates the distance between two points. Put
|
||||
it into your `src/main.rs`:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x1 = 0.0;
|
||||
let y1 = 5.0;
|
||||
|
||||
let x2 = 12.0;
|
||||
let y2 = 0.0;
|
||||
|
||||
let answer = distance(x1, y1, x2, y2);
|
||||
|
||||
println!("Point 1: ({}, {})", x1, y1);
|
||||
println!("Point 2: ({}, {})", x2, y2);
|
||||
println!("Distance: {}", answer);
|
||||
}
|
||||
|
||||
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
|
||||
let x_squared = f64::powi(x2 - x1, 2);
|
||||
let y_squared = f64::powi(y2 - y1, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
```
|
||||
|
||||
Let's try running this program with `cargo run`:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling points v0.1.0 (file:///projects/points)
|
||||
Running `target/debug/points`
|
||||
Point 1: (0, 5)
|
||||
Point 2: (12, 0)
|
||||
Distance: 13
|
||||
```
|
||||
|
||||
Let's take a quick look at `distance()` before we move forward:
|
||||
|
||||
```rust
|
||||
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
|
||||
let x_squared = f64::powi(x2 - x1, 2);
|
||||
let y_squared = f64::powi(y2 - y1, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
```
|
||||
|
||||
To find the distance between two points, we can use the Pythagorean Theorem.
|
||||
The theorem is named after Pythagoras, who was the first person to mathematically
|
||||
prove this formula. The details aren't that important, to be honest. There's a few
|
||||
things that we haven't discussed yet, though.
|
||||
|
||||
```rust,ignore
|
||||
f64::powi(2.0, 3)
|
||||
```
|
||||
|
||||
The double colon (`::`) here is a namespace operator. We haven’t talked about
|
||||
modules yet, but you can think of the `powi()` function as being scoped inside
|
||||
of another name. In this case, the name is `f64`, the same as the type. The
|
||||
`powi()` function takes two arguments: the first is a number, and the second is
|
||||
the power that it raises that number to. In this case, the second number is an
|
||||
integer, hence the ‘i’ in its name. Similarly, `sqrt()` is a function under the
|
||||
`f64` module, which takes the square root of its argument.
|
||||
|
||||
## Why `struct`s?
|
||||
|
||||
Our little program is okay, but we can do better. The key is in the signature
|
||||
of `distance()`:
|
||||
|
||||
```rust,ignore
|
||||
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
|
||||
```
|
||||
|
||||
The distance function is supposed to calculate the distance between two points.
|
||||
But our distance function calculates some distance between four numbers. The
|
||||
first two and last two arguments are related, but that’s not expressed anywhere
|
||||
in our program itself. We need a way to group `(x1, y1)` and `(x2, y2)`
|
||||
together.
|
||||
|
||||
We’ve already discussed one way to do that: tuples. Here’s a version of our program
|
||||
which uses tuples:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let p1 = (0.0, 5.0);
|
||||
|
||||
let p2 = (12.0, 0.0);
|
||||
|
||||
let answer = distance(p1, p2);
|
||||
|
||||
println!("Point 1: {:?}", p1);
|
||||
println!("Point 2: {:?}", p2);
|
||||
println!("Distance: {}", answer);
|
||||
}
|
||||
|
||||
fn distance(p1: (f64, f64), p2: (f64, f64)) -> f64 {
|
||||
let x_squared = f64::powi(p2.0 - p1.0, 2);
|
||||
let y_squared = f64::powi(p2.1 - p1.1, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
```
|
||||
|
||||
This is a little better, for sure. Tuples let us add a little bit of structure.
|
||||
We’re now passing two arguments, so that’s more clear. But it’s also worse.
|
||||
Tuples don’t give names to their elements, and so our calculation has gotten
|
||||
much more confusing:
|
||||
|
||||
```rust,ignore
|
||||
p2.0 - p1.0
|
||||
p2.1 - p1.1
|
||||
```
|
||||
|
||||
When writing this example, your authors almost got it wrong themselves! Distance
|
||||
is all about `x` and `y` points, but now it’s all about `0` and `1`. This isn’t
|
||||
great.
|
||||
|
||||
Enter `struct`s. We can transform our tuples into something with a name:
|
||||
|
||||
```rust,ignore
|
||||
let p1 = (0.0, 5.0);
|
||||
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
let p1 = Point { x: 0.0, y: 5.0 };
|
||||
```
|
||||
|
||||
Here’s what declaring a `struct` looks like:
|
||||
|
||||
```text
|
||||
struct NAME {
|
||||
NAME: TYPE,
|
||||
}
|
||||
```
|
||||
|
||||
The `NAME: TYPE` bit is called a ‘field’, and we can have as many or as few of
|
||||
them as you’d like. If you have none of them, drop the `{}`s:
|
||||
|
||||
```rust
|
||||
struct Foo;
|
||||
```
|
||||
|
||||
`struct`s with no fields are called ‘unit structs’, and are used in certain
|
||||
advanced situations. We will just ignore them for now.
|
||||
|
||||
You can access the field of a struct in the same way you access an element of
|
||||
a tuple, except you use its name:
|
||||
|
||||
```rust,ignore
|
||||
let p1 = (0.0, 5.0);
|
||||
let x = p1.0;
|
||||
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
let p1 = Point { x: 0.0, y: 5.0 };
|
||||
let x = p1.x;
|
||||
```
|
||||
|
||||
Let’s convert our program to use our `Point` `struct`. Here’s what it looks
|
||||
like now:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p1 = Point { x: 0.0, y: 5.0};
|
||||
|
||||
let p2 = Point { x: 12.0, y: 0.0};
|
||||
|
||||
let answer = distance(p1, p2);
|
||||
|
||||
println!("Point 1: {:?}", p1);
|
||||
println!("Point 2: {:?}", p2);
|
||||
println!("Distance: {}", answer);
|
||||
}
|
||||
|
||||
fn distance(p1: Point, p2: Point) -> f64 {
|
||||
let x_squared = f64::powi(p2.x - p1.x, 2);
|
||||
let y_squared = f64::powi(p2.y - p1.y, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
```
|
||||
|
||||
Our function signature for `distance()` now says exactly what we mean: it
|
||||
calculates the distance between two `Point`s. And rather than `0` and `1`,
|
||||
we’ve got back our `x` and `y`. This is a win for clarity.
|
||||
|
||||
There’s one other thing that’s a bit strange here, this annotation on our
|
||||
`struct` declaration:
|
||||
|
||||
```rust,ignore
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point {
|
||||
```
|
||||
|
||||
We haven’t yet talked about traits, but we did talk about `Debug` when we
|
||||
discussed arrays. This `derive` attribute allows us to tweak the behavior of
|
||||
our `Point`. In this case, we are opting into copy semantics, and everything
|
||||
that implements `Copy` must implement `Clone`.
|
||||
# Method Syntax
|
||||
|
||||
In the last section on ownership, we made several references to ‘methods’.
|
||||
Methods look like this:
|
||||
|
||||
```rust
|
||||
let s1 = String::from("hello");
|
||||
|
||||
// call a method on our String
|
||||
let s2 = s1.clone();
|
||||
|
||||
println!("{}", s1);
|
||||
```
|
||||
|
||||
The call to `clone()` is attatched to `s1` with a dot. This is called ‘method
|
||||
syntax’, and it’s a way to call certain functions with a different style.
|
||||
|
||||
Why have two ways to call functions? We’ll talk about some deeper reasons
|
||||
related to ownership in a moment, but one big reason is that methods look nicer
|
||||
when chained together:
|
||||
|
||||
```rust,ignore
|
||||
// with functions
|
||||
h(g(f(x)));
|
||||
|
||||
// with methods
|
||||
x.f().g().h();
|
||||
```
|
||||
|
||||
The nested-functions version reads in reverse: we call `f()`, then `g()`, then
|
||||
`h()`, but it reads as `h()`, then `g()`, then `f()`.
|
||||
|
||||
Before we get into the details, let’s talk about how to define your own
|
||||
methods.
|
||||
|
||||
## Defining methods
|
||||
|
||||
We can define methods with the `impl` keyword. `impl` is short for
|
||||
‘implementation’. Doing so looks like this:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
fn distance(&self, other: &Point) -> f64 {
|
||||
let x_squared = f64::powi(other.x - self.x, 2);
|
||||
let y_squared = f64::powi(other.y - self.y, 2);
|
||||
|
||||
f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
}
|
||||
|
||||
let p1 = Point { x: 0.0, y: 0.0 };
|
||||
let p2 = Point { x: 5.0, y: 6.5 };
|
||||
|
||||
assert_eq!(8.200609733428363, p1.distance(&p2));
|
||||
```
|
||||
|
||||
Let’s break this down. First, we have our `Point` struct from earlier in the
|
||||
chapter. Next comes our first use of the `impl` keyword:
|
||||
|
||||
```
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point {
|
||||
# x: f64,
|
||||
# y: f64,
|
||||
# }
|
||||
#
|
||||
impl Point {
|
||||
# fn distance(&self, other: &Point) -> f64 {
|
||||
# let x_squared = f64::powi(other.x - self.x, 2);
|
||||
# let y_squared = f64::powi(other.y - self.y, 2);
|
||||
#
|
||||
# f64::sqrt(x_squared + y_squared)
|
||||
# }
|
||||
}
|
||||
#
|
||||
# let p1 = Point { x: 0.0, y: 0.0 };
|
||||
# let p2 = Point { x: 5.0, y: 6.5 };
|
||||
#
|
||||
# assert_eq!(8.200609733428363, p1.distance(&p2));
|
||||
```
|
||||
|
||||
Everything we put inside of the curly braces will be methods implemented on
|
||||
`Point`.
|
||||
|
||||
```
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point {
|
||||
# x: f64,
|
||||
# y: f64,
|
||||
# }
|
||||
#
|
||||
# impl Point {
|
||||
fn distance(&self, other: &Point) -> f64 {
|
||||
# let x_squared = f64::powi(other.x - self.x, 2);
|
||||
# let y_squared = f64::powi(other.y - self.y, 2);
|
||||
#
|
||||
# f64::sqrt(x_squared + y_squared)
|
||||
}
|
||||
# }
|
||||
#
|
||||
# let p1 = Point { x: 0.0, y: 0.0 };
|
||||
# let p2 = Point { x: 5.0, y: 6.5 };
|
||||
#
|
||||
# assert_eq!(8.200609733428363, p1.distance(&p2));
|
||||
```
|
||||
|
||||
Next is our definition. This looks very similar to our previous definition of
|
||||
`distance()` as a function:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point {
|
||||
# x: f64,
|
||||
# y: f64,
|
||||
# }
|
||||
fn distance(p1: Point, p2: Point) -> f64 {
|
||||
# let x_squared = f64::powi(p2.x - p1.x, 2);
|
||||
# let y_squared = f64::powi(p2.y - p1.y, 2);
|
||||
#
|
||||
# f64::sqrt(x_squared + y_squared)
|
||||
# }
|
||||
```
|
||||
|
||||
Other than this, the rest of the example is familliar: an implementation of
|
||||
`distance()`, and using the method to find an answer.
|
||||
|
||||
There are two differences. The first is in the first argument. Instead of a name
|
||||
and a type, we have written `&self`. This is what distinguishes a method from a
|
||||
function: using `self` inside of an `impl` block. Because we already know that
|
||||
we are implementing this method on `Point`, we don’t need to write the type of
|
||||
`self` out. However, we have written `&self`, not only `self`. This is because
|
||||
we want to take our argument by reference rather than by ownership. In other
|
||||
words, these two forms are the same:
|
||||
|
||||
```rust,ignore
|
||||
fn foo(self: &Point)
|
||||
fn foo(&self)
|
||||
```
|
||||
|
||||
Just like any other parameter, you can take `self` in three forms. Here’s the
|
||||
list, with the most common form first:
|
||||
|
||||
```rust,ignore
|
||||
fn foo(&self) // take self by reference
|
||||
fn foo(&mut self) // take self by mutable reference
|
||||
fn foo(self) // take self by ownership
|
||||
```
|
||||
|
||||
In this case, we only need a reference. We don’t plan on taking ownership, and
|
||||
we don’t need to mutate either point. Taking by reference is by far the most
|
||||
common form of method, followed by a mutable reference, and then occasionally
|
||||
by ownership.
|
||||
|
||||
### Methods and automatic referencing
|
||||
|
||||
We’ve left out an important detail. It’s in this line of the example:
|
||||
|
||||
```
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point {
|
||||
# x: f64,
|
||||
# y: f64,
|
||||
# }
|
||||
#
|
||||
# impl Point {
|
||||
# fn distance(&self, other: &Point) -> f64 {
|
||||
# let x_squared = f64::powi(other.x - self.x, 2);
|
||||
# let y_squared = f64::powi(other.y - self.y, 2);
|
||||
#
|
||||
# f64::sqrt(x_squared + y_squared)
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# let p1 = Point { x: 0.0, y: 0.0 };
|
||||
# let p2 = Point { x: 5.0, y: 6.5 };
|
||||
#
|
||||
assert_eq!(8.200609733428363, p1.distance(&p2));
|
||||
```
|
||||
|
||||
When we defined `distance()`, we took both `self` and the other argument by
|
||||
reference. Yet, we needed a `&` for `p2` but not `p1`. What gives?
|
||||
|
||||
This feature is called ‘automatic referencing’, and calling methods is one
|
||||
of the few places in Rust that has behavior like this. Here’s how it works:
|
||||
when you call a method with `self.(`, Rust will automatically add in `&`s
|
||||
or `&mut`s to match the signature. In other words, these three are the same:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point {
|
||||
# x: f64,
|
||||
# y: f64,
|
||||
# }
|
||||
#
|
||||
# impl Point {
|
||||
# fn distance(&self, other: &Point) -> f64 {
|
||||
# let x_squared = f64::powi(other.x - self.x, 2);
|
||||
# let y_squared = f64::powi(other.y - self.y, 2);
|
||||
#
|
||||
# f64::sqrt(x_squared + y_squared)
|
||||
# }
|
||||
# }
|
||||
# let p1 = Point { x: 0.0, y: 0.0 };
|
||||
# let p2 = Point { x: 5.0, y: 6.5 };
|
||||
p1.distance(&p2);
|
||||
(&p1).distance(&p2);
|
||||
Point::distance(&p1, &p2);
|
||||
```
|
||||
|
||||
The first one looks much, much cleaner. Here’s another example:
|
||||
|
||||
```rust
|
||||
let mut s = String::from("Hello,");
|
||||
|
||||
s.push_str(" world!");
|
||||
|
||||
// The above is the same as:
|
||||
// (&mut s).push_str(" world!");
|
||||
|
||||
assert_eq!("Hello, world!", s);
|
||||
```
|
||||
|
||||
Because [`push_str()`] has the following signature:
|
||||
|
||||
```rust,ignore
|
||||
fn push_str(&mut self, string: &str) {
|
||||
```
|
||||
|
||||
[`push_str()`]: http://doc.rust-lang.org/collections/string/struct.String.html#method.push_str
|
||||
|
||||
This automatic referencing behavior works because methods have a clear receiver
|
||||
— the type of `self` — and in most cases it’s clear given the receiver and name
|
||||
of a method whether the method is just reading (so needs `&self`), mutating (so
|
||||
`&mut self`), or consuming (so `self`). The fact that Rust makes borrowing
|
||||
implicit for method receivers is a big part of making ownership ergonomic in
|
||||
practice.
|
||||
|
||||
## Methods can be called like functions
|
||||
|
||||
Furthermore, if we have a method, we can also call it like a function:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point {
|
||||
# x: f64,
|
||||
# y: f64,
|
||||
# }
|
||||
#
|
||||
# impl Point {
|
||||
# fn distance(&self, other: &Point) -> f64 {
|
||||
# let x_squared = f64::powi(other.x - self.x, 2);
|
||||
# let y_squared = f64::powi(other.y - self.y, 2);
|
||||
#
|
||||
# f64::sqrt(x_squared + y_squared)
|
||||
# }
|
||||
# }
|
||||
# let p1 = Point { x: 0.0, y: 0.0 };
|
||||
# let p2 = Point { x: 5.0, y: 6.5 };
|
||||
let d1 = p1.distance(&p2);
|
||||
let d2 = Point::distance(&p1, &p2);
|
||||
|
||||
assert_eq!(d1, d2);
|
||||
```
|
||||
|
||||
Instead of using `self.(`, we use `Point` and the namespace operator to call it
|
||||
like a function instead. Because functions do not do the automatic referencing,
|
||||
we must pass in `&p1` explicitly.
|
||||
|
||||
While methods can be called like functions, functions cannot be called like
|
||||
methods. If the first argument isn’t named `self`, it cannot be called like a
|
||||
method.
|
||||
# Generics
|
||||
|
||||
We've been working with a `Point` struct that looks like this:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
```
|
||||
|
||||
But what if we didn't want to always use an `f64` here? What about an `f32` for
|
||||
when we need less precision? Or an `i32` if we only want integer coordinates?
|
||||
|
||||
While our simple `Point` struct may be a bit too simple to bother making
|
||||
generic in a real application, we're going to stick with it to show you the
|
||||
syntax. Especially when building library code, generics allow for more code
|
||||
re-use, and unlock a lot of powerful techniques.
|
||||
|
||||
## Generic data types
|
||||
|
||||
'Generics' let us write code that allows for several different types, while
|
||||
letting us have one definition. A more generic `Point` would look like this:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
```
|
||||
|
||||
There are two changes here, and they both involve this new `T`. The first change
|
||||
is in the definition:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
struct Point<T> {
|
||||
# x: T,
|
||||
# y: T,
|
||||
# }
|
||||
```
|
||||
|
||||
Our previous definition said, "We are defining a struct named Point." This
|
||||
definition says something slightly different: "We are defining a struct named
|
||||
Point with one type parameter `T`."
|
||||
|
||||
Let's talk about this term 'type parameter'. We've already seen one other thing
|
||||
called a 'parameter' in Rust: function parameters:
|
||||
|
||||
```rust
|
||||
fn plus_one(x: i32) -> i32 {
|
||||
x + 1
|
||||
}
|
||||
```
|
||||
|
||||
Here, `x` is a parameter to this function. We can call this function with a
|
||||
different value, and `x` will change each time it's called:
|
||||
|
||||
```rust
|
||||
# fn plus_one(x: i32) -> i32 {
|
||||
# x + 1
|
||||
# }
|
||||
let six = plus_one(5);
|
||||
let eleven = plus_one(10);
|
||||
```
|
||||
|
||||
In the same way, a type parameter allows us to define a data type which can be
|
||||
different each time we use it:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
let integral_point = Point { x: 5, y: 5 };
|
||||
let floating_point = Point { x: 5.0, y: 5.0 };
|
||||
```
|
||||
|
||||
Here, `integral_point` uses `i32` values for `T`, and `floating_point` uses
|
||||
`f64` values. This also leads us to talk about the second change we made to `Point`:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
# }
|
||||
```
|
||||
|
||||
Instead of saying `x: i32`, we say `x: T`. This `T` is the same one that we
|
||||
used above in the struct declaration. Because `x` and `y` both use `T`, they'll
|
||||
be the same type. We could give them different types:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point<T, OtherT> {
|
||||
x: T,
|
||||
y: OtherT,
|
||||
}
|
||||
|
||||
let different = Point { x: 5, y: 5.0 };
|
||||
let same = Point { x: 5.0, y: 5.0 };
|
||||
```
|
||||
|
||||
Here, instead of a single parameter, `T`, we have two: `T` and `OtherT`. Type
|
||||
parameters have the same naming convention as other types: `CamelCase`.
|
||||
However, you'll often see short, one-letter names used for types. `T` is very
|
||||
common, because it's short for 'type', but you can name them something longer
|
||||
if you'd like. In this version of `Point`, we say that `x` has the type `T`,
|
||||
and `y` has the type `OtherT`. This lets us give them two different types, but
|
||||
they don't have to be.
|
||||
|
||||
## Generic functions
|
||||
|
||||
Regular old functions can also take generic parameters, with a syntax that looks
|
||||
very similar:
|
||||
|
||||
```rust
|
||||
fn foo<T>(x: T) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This `foo()` function has one generic parameter, `T`, and takes one argument,
|
||||
`x`, which has the type `T`. Let's talk a little bit more about what this means.
|
||||
|
||||
|
||||
## Generic methods
|
||||
|
||||
We've seen how to define methods with the `impl` keyword. Our generic `Point`
|
||||
can have generic methods, too:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T> Point<T> {
|
||||
fn some_method(&self) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We also need the `<T>` after `impl`. This line reads, "We will be implementing
|
||||
methods with one generic type parameter, `T`, for a type, `Point`, which takes
|
||||
one generic type `T`." In a sense, the `impl<T>` says "we will be using a type
|
||||
`T`" and the `Point<T>` says "that `T` is used for `Point`." In this simple
|
||||
case, this syntax can feel a bit redundant, but when we get into some of Rust's
|
||||
more advanced features later, this distinction will become more useful.
|
||||
|
||||
## There's more to the story
|
||||
|
||||
This section covered the basic syntax of generics, but it's not the full story.
|
||||
For example, let's try to implement our `foo()` function: we'll have it print out
|
||||
the value of `x`:
|
||||
|
||||
```rust,ignore
|
||||
fn foo<T>(x: T) {
|
||||
println!("x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
We'll get an error:
|
||||
|
||||
```text
|
||||
error: the trait `core::fmt::Display` is not implemented for the type `T` [E0277]
|
||||
println!("x is: {}", x);
|
||||
^
|
||||
```
|
||||
|
||||
We can't print out `x`! The error messages reference something we talked about
|
||||
breifly before, the `Display` trait. In order to implement this function, we
|
||||
need to talk about traits. But we only need to talk about traits to implement
|
||||
our own generic functions; we don't need this understanding to use them. So
|
||||
rather than get into more details about this right now, let's talk about other
|
||||
useful Rust data types, and we can come back to implementing generic functions
|
||||
in the chapter about traits.
|
||||
|
||||
For now, the important bits to understand:
|
||||
|
||||
* Generic type parameters are kind of like function parameters, but for types
|
||||
instead of values.
|
||||
* Type parameters go inside `<>`s and are usually named things like `T`.
|
||||
|
||||
With that, let's talk about another fundamental Rust data type: enums.
|
@ -1,738 +0,0 @@
|
||||
# Enums
|
||||
|
||||
Next, let’s look at a feature of Rust that’s similar to structs, but also
|
||||
different. Enumerations, or ‘enums’ as they’re more commonly referred to,
|
||||
are an extremely powerful feature of Rust. Enums are a feature that are in many
|
||||
languages, but what they can do is different per-language. Rust’s enums are
|
||||
most similar to enums in functional languages.
|
||||
|
||||
Here’s an example of an enum:
|
||||
|
||||
```rust
|
||||
enum IpAddrKind {
|
||||
V4,
|
||||
V6,
|
||||
}
|
||||
```
|
||||
|
||||
This enum represents the kind of an IP address. There are two major standards
|
||||
used for IP addresses: version four, and version six. Any IP address can be either
|
||||
a version four address, or a version six address. But it cannot be both kinds at
|
||||
the same time. This is where enums get their name: they allow us to enumerate all
|
||||
of the possible kinds that our value can have.
|
||||
|
||||
We can create values of `IpAddrKind` like this:
|
||||
|
||||
```rust
|
||||
# enum IpAddrKind {
|
||||
# V4,
|
||||
# V6,
|
||||
# }
|
||||
|
||||
let four = IpAddrKind::V4;
|
||||
let six = IpAddrKind::V6;
|
||||
```
|
||||
|
||||
Note that the variants of the enum are namespaced under its name, and we use
|
||||
the double colon to separate the two.
|
||||
|
||||
Enums have more tricks up their sleeves, however. Thinking more about our IP
|
||||
address type, we don’t have a way to store the actual data of the IP address,
|
||||
we only know what kind it is. Given that you just learned about structs, you
|
||||
might tackle this problem like this:
|
||||
|
||||
```rust
|
||||
enum IpAddrKind {
|
||||
V4,
|
||||
V6,
|
||||
}
|
||||
|
||||
struct IpAddr {
|
||||
kind: IpAddrKind,
|
||||
address: String,
|
||||
}
|
||||
|
||||
let home = IpAddr {
|
||||
kind: IpAddrKind::V4,
|
||||
address: String::from("127.0.0.1"),
|
||||
};
|
||||
|
||||
let loopback = IpAddr {
|
||||
kind: IpAddrKind::V6,
|
||||
address: String::from("::1"),
|
||||
};
|
||||
```
|
||||
|
||||
We’ve used a struct to bundle the two values together: now we keep the kind
|
||||
with the value itself. This design isn’t bad, exactly, but it wouldn’t be
|
||||
considered idiomatic Rust. We can represent the same thing with just an enum:
|
||||
|
||||
```rust
|
||||
enum IpAddr {
|
||||
V4(String),
|
||||
V6(String),
|
||||
}
|
||||
|
||||
let home = IpAddr::V4(String::from("127.0.0.1"));
|
||||
|
||||
let loopback = IpAddr::V6(String::from("::1"));
|
||||
```
|
||||
|
||||
We can attach data to each variant of the enum directly. No need for an extra
|
||||
struct. But beyond that, this approach is better than using a struct alongside
|
||||
our enum because we can attatch different kinds of data to each variant.
|
||||
Imagine that instead of a `String`, we would prefer to store a `V4` as its four
|
||||
individual components, while leaving the `V6` variant as a `String`. With our
|
||||
struct, we’d be stuck. But enums deal with this case with ease:
|
||||
|
||||
```rust
|
||||
enum IpAddr {
|
||||
V4(u32, u32, u32, u32),
|
||||
V6(String),
|
||||
}
|
||||
|
||||
let home = IpAddr::V4(127, 0, 0, 1);
|
||||
|
||||
let loopback = IpAddr::V6(String::from("::1"));
|
||||
```
|
||||
|
||||
You can put any kind of data inside of an enum variant, including another enum!
|
||||
The `IpAddr` enum is [in the standard library][IpAddr], but it embeds two different
|
||||
structs inside of its variants:
|
||||
|
||||
```rust
|
||||
struct Ipv4Addr {
|
||||
// details elided
|
||||
}
|
||||
|
||||
struct Ipv6Addr {
|
||||
// details elided
|
||||
}
|
||||
|
||||
enum IpAddr {
|
||||
V4(Ipv4Addr),
|
||||
V6(Ipv6Addr),
|
||||
}
|
||||
```
|
||||
|
||||
[IpAddr]: http://doc.rust-lang.org/std/net/enum.IpAddr.html
|
||||
|
||||
Here’s an enum with a variety of types embedded in its variants:
|
||||
|
||||
```rust
|
||||
enum Message {
|
||||
Quit,
|
||||
Move { x: i32, y: i32 },
|
||||
Write(String),
|
||||
ChangeColor(i32, i32, i32),
|
||||
}
|
||||
```
|
||||
|
||||
* `Quit` has no data associated with it at all.
|
||||
* `Move` includes an anonymous struct inside of it.
|
||||
* `Write` includes a single `String`.
|
||||
* `ChangeColor` includes three `i32`s.
|
||||
|
||||
We haven’t talked a lot about how to access the data inside an enum variant,
|
||||
however. To do that, let’s move on to some new Rust syntax that’s especially
|
||||
useful with enums: `match`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Option
|
||||
|
||||
Now that we have a handle on enums, let's combine them with a feature that we
|
||||
talked a little bit about in the previous chapter: generics.
|
||||
|
||||
Programming language design is often though of as which features you include,
|
||||
but it's also about which features you leave out. Rust does not have a feature
|
||||
that is in many other languages: 'null'. In languages with this feature,
|
||||
variables can have two states: null or not-null.
|
||||
|
||||
The inventor of this concept has this to say:
|
||||
|
||||
> I call it my billion-dollar mistake. At that time, I was designing the first
|
||||
> comprehensive type system for references in an object-oriented language. My
|
||||
> goal was to ensure that all use of references should be absolutely safe, with
|
||||
> checking performed automatically by the compiler. But I couldn't resist the
|
||||
> temptation to put in a null reference, simply because it was so easy to
|
||||
> implement. This has led to innumerable errors, vulnerabilities, and system
|
||||
> crashes, which have probably caused a billion dollars of pain and damage in
|
||||
> the last forty years.
|
||||
>
|
||||
> - Tony Hoare "Null References: The Billion Dollar Mistake"
|
||||
|
||||
The problem with null values is twofold: first, a value can be null or not, at
|
||||
any time. The second is that if you try to use a value that's null, you'll get
|
||||
an error of some kind, depending on the language. Because this property is
|
||||
pervasive, it's extremely easy to make this kind of error.
|
||||
|
||||
Even with these problems, the concept that null is trying to express is still a
|
||||
useful one: this is a value which is currently invalid or not present for some
|
||||
reason. The problem isn't with the concept itself, but with the particular
|
||||
implementation. As such, Rust does not have the concept of null, but we do have
|
||||
a type which can encode the concept of a value being present. We call this type
|
||||
`Option<T>`, and it looks like this:
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None,
|
||||
}
|
||||
```
|
||||
|
||||
This type is [provided by the standard library][option], and is so useful that
|
||||
it's even in the prelude; you don't need to import it explicitly. Furthermore,
|
||||
so are its variants: you can say `Some` and `None` directly, without prefixing
|
||||
them with `Option::`.
|
||||
|
||||
[option]: ../std/option/enum.Option.html
|
||||
|
||||
Here's an example of using `Option<T>`:
|
||||
|
||||
```rust
|
||||
let some_number = Some(5);
|
||||
let some_string = Some("a string");
|
||||
|
||||
// If we only say None, we need to tell Rust what type of Option<T> we have.
|
||||
let absent_number: Option<i32> = None;
|
||||
```
|
||||
|
||||
Let's dig in. First, you'll notice that we used the `<T>` syntax when defining
|
||||
`Option<T>`: it's a generic enum. `Option<T>` has two variants: `Some`, which
|
||||
contains a `T`, and `None`, which has no data associated with it. In some
|
||||
sense, `None` means 'null', and `Some` means 'not null'. So why is this any
|
||||
better than null?
|
||||
|
||||
In short, because `Option<T>` and `T` are different types. That's a bit too
|
||||
short though. Here's an example:
|
||||
|
||||
```rust,ignore
|
||||
let x = 5;
|
||||
let y = Some(5);
|
||||
|
||||
let sum = x + y;
|
||||
```
|
||||
|
||||
This will not compile. We get an error message like this:
|
||||
|
||||
```text
|
||||
error: the trait `core::ops::Add<core::option::Option<_>>` is not implemented
|
||||
for the type `_` [E0277]
|
||||
|
||||
let sum = x + y;
|
||||
^~~~~
|
||||
```
|
||||
|
||||
Intense! What this error message is trying to say is that Rust does not
|
||||
understand how to add an `Option<T>` and a `T`. They're different types! This
|
||||
shows one of the big advantages of an `Option<T>` type: if you have a type that
|
||||
may or may not exist, you have to deal with that fact before you can assume it
|
||||
exists. In other words, you have to convert an `Option<T>` to a `T` before you
|
||||
can do `T` stuff with it. This helps catch one of the most common issues with
|
||||
null, generally: assuming that something isn't null, when it actually is.
|
||||
|
||||
So, how _do_ you get a `T` from an `Option<T>`? The option type has a large
|
||||
number of methods that you can check out in [its documentation], and becoming
|
||||
familiar with them will be extremely useful in your journey with Rust.
|
||||
|
||||
[its documentation]: ../std/option/enum.Option.html
|
||||
|
||||
But we want a deeper understanding than that. If we didn't have those methods
|
||||
defined for us already, what would we do? For that, we need a new feature: `match`.
|
||||
# Match
|
||||
|
||||
Rust has an extremely powerful control-flow operator: `match`. It allows us to
|
||||
compare a value against a series of patterns, and then execute code based on
|
||||
how they compare. Remember the `Option<T>` type from the previous section?
|
||||
Let's say that we want to write a function that takes an `Option<i32>`, and
|
||||
if there's a value inside, add one to it.
|
||||
|
||||
This function is very easy to write, thanks to `match`. It looks like this:
|
||||
|
||||
```rust
|
||||
fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
match x {
|
||||
None => None,
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
}
|
||||
|
||||
let five = Some(5);
|
||||
let six = plus_one(five);
|
||||
let none = plus_one(None);
|
||||
```
|
||||
|
||||
Let's break down the `match`! At a high-level, the `match` expression looks
|
||||
like this:
|
||||
|
||||
```text
|
||||
match condition {
|
||||
pattern => code,
|
||||
}
|
||||
```
|
||||
|
||||
First, we have the `match` keyword. Next, we have a condition. This feels very
|
||||
similar to an `if` expression, but there's a big difference: with `if`, the
|
||||
condition needs to be a boolean. Here, it can be any type.
|
||||
|
||||
Next, we have a "match arm". That's the part that looks like `pattern =>
|
||||
code,`. We can have as many arms as we need to: our `match` above has two
|
||||
arms. An arm has two parts: a pattern, and some code. When the `match`
|
||||
expression executes, it compares the condition against the pattern of each arm,
|
||||
in turn. If the pattern matches the condition, the associated code is executed,
|
||||
and the rest of the patterns are not checked. If it doesn't match, execution
|
||||
continues to the next arm.
|
||||
|
||||
Let's examine the first execution of `plus_one()` in more detail. In the above
|
||||
example, `x` will be `Some(5)`. Let's compare that against each arm:
|
||||
|
||||
```text
|
||||
None => None,
|
||||
```
|
||||
|
||||
Does `Some(5)` match `None`? No, it's the wrong variant. So let's continue.
|
||||
|
||||
```text
|
||||
Some(i) => Some(i + 1),
|
||||
```
|
||||
|
||||
Does `Some(5)` match `Some(i)`? Why yes it does! We have the same variant. But
|
||||
what about `i`? In a pattern like this, we can declare new bindings, similarly
|
||||
to what we did with `let`. So in this case, the code part of the match arm will
|
||||
have a binding, `i`, which corresponds to the `5`.
|
||||
|
||||
With this arm, the code portion is `Some(i + 1)`. So we do exactly that: we
|
||||
take `i`, which is `5`, add one to it, and create a new `Some` value with our
|
||||
sum inside.
|
||||
|
||||
Because `match` is an expression, the value of the overall expression becomes
|
||||
the value of the arm that executed. So the value of this `match` expression
|
||||
will be `Some(6)`. And since our `match` is the only expression in the
|
||||
function, the value of the `match` will be the value of the function, and so
|
||||
`Some(6)` is our return value as well, which is exactly what we were shooting
|
||||
for.
|
||||
|
||||
Now let's consider the second call. In this case, `x` is `None`. We enter the
|
||||
`match`, and compare to the first arm:
|
||||
|
||||
```text
|
||||
None => None,
|
||||
```
|
||||
|
||||
Does `None` match `None`? Yup! And so we return `None`. There's no value to add
|
||||
to.
|
||||
|
||||
Combining `match` and enums together is extremely powerful. You'll see this
|
||||
pattern a lot in Rust code: `match` against an enum, binding to the data
|
||||
inside, and then executing code based on it. It's a bit tricky at first, but
|
||||
once you get used to it, you'll wish you had it in languages that don't support
|
||||
it. It's consistently a user favorite.
|
||||
|
||||
## Matches are exhaustive
|
||||
|
||||
There's one other aspect of `match` we didn't talk about. Consider this version
|
||||
of `plus_one()`:
|
||||
|
||||
```rust,ignore
|
||||
fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
match x {
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A bug! We didn't handle the `None` case. Luckily, it's a bug Rust knows how to catch.
|
||||
If we try to compile this code, we'll get an error:
|
||||
|
||||
```text
|
||||
error: non-exhaustive patterns: `None` not covered [E0004]
|
||||
match x {
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
```
|
||||
|
||||
Rust knows that we did not cover every possible option, and even knows which
|
||||
pattern we forgot! This is referred to as being "exhaustive", we must exhaust
|
||||
every last option possible in order to be valid!
|
||||
|
||||
This analysis isn't perfect, however. This will also error:
|
||||
|
||||
```rust,ignore
|
||||
# let some_u8_value = 0u8;
|
||||
match some_u8_value {
|
||||
0 => println!("zero"),
|
||||
1 => println!("one"),
|
||||
2 => println!("two"),
|
||||
3 => println!("three"),
|
||||
4 => println!("four"),
|
||||
5 => println!("five"),
|
||||
6 => println!("six"),
|
||||
7 => println!("seven"),
|
||||
// We won't write out all of the arms here, but imagine that there are more
|
||||
// arms corresponding to the rest of the numbers.
|
||||
254 => println!("two-hundred and fifty-four"),
|
||||
255 => println!("two-hundred and fifty-five"),
|
||||
}
|
||||
```
|
||||
|
||||
Even though a `u8` can only have valid values of zero through 255, Rust isn't
|
||||
quite smart enough to understand we've covered all the cases. In order to fix
|
||||
this, we can use a special pattern, `_`:
|
||||
|
||||
```rust
|
||||
# let some_u8_value = 0u8;
|
||||
match some_u8_value {
|
||||
0 => println!("zero"),
|
||||
1 => println!("one"),
|
||||
2 => println!("two"),
|
||||
3 => println!("three"),
|
||||
4 => println!("four"),
|
||||
5 => println!("five"),
|
||||
6 => println!("six"),
|
||||
7 => println!("seven"),
|
||||
// ...
|
||||
254 => println!("two-hundred and fifty-four"),
|
||||
255 => println!("two-hundred and fifty-five"),
|
||||
_ => panic!("can't ever happen"),
|
||||
}
|
||||
```
|
||||
|
||||
The `_` pattern matches anything at all, and so with it as the final pattern,
|
||||
Rust can understand that we have all our bases covered. It's not only used for
|
||||
this sort of exhastiveness issue, though. It's useful any time we don't want to
|
||||
deal with a number of cases. Consider this scenario: if we wanted to print out
|
||||
something one one, three, five, and seven:
|
||||
|
||||
```rust
|
||||
# let some_u8_value = 0u8;
|
||||
match some_u8_value {
|
||||
1 => println!("one"),
|
||||
3 => println!("three"),
|
||||
5 => println!("five"),
|
||||
7 => println!("seven"),
|
||||
_ => (),
|
||||
}
|
||||
```
|
||||
|
||||
The `_` pattern will match all the other cases, and `()` will do nothing, it's
|
||||
the unit value.
|
||||
|
||||
## More about patterns
|
||||
|
||||
As we've just seen, patterns are powerful, yet complex. Let's take a whole
|
||||
section to cover all of the things that they can do.
|
||||
# if let
|
||||
|
||||
There's one more advanced control flow structure we haven't discussed: `if
|
||||
let`. Imagine we're in a situation like this:
|
||||
|
||||
```rust
|
||||
# let some_option = Some(5);
|
||||
match some_option {
|
||||
Some(x) => {
|
||||
// do something with x
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
```
|
||||
|
||||
We care about the `Some` case, but don't want to do anything with the `None`
|
||||
case. With an `Option`, this isn't _too_ bad, but with a more complex enum,
|
||||
adding `_ => {}` after processing just one variant doesn't feel great. We have
|
||||
this boilerplate arm, and we have an extra level of indentation: the code that
|
||||
does something with `x` is indented twice, rather than just once. We really want
|
||||
a construct that says "Do something with this one case, I don't care about the
|
||||
others."
|
||||
|
||||
Enter `if let`:
|
||||
|
||||
```rust
|
||||
# let some_option = Some(5);
|
||||
if let Some(x) = some_option {
|
||||
// do something with x
|
||||
}
|
||||
```
|
||||
|
||||
`if let` takes a pattern and an expression, separated by an `=`. It works
|
||||
exactly like a `match`, where the expression is given to the `match` and the
|
||||
pattern is its first arm. In other words, you can think of `if let` as syntax
|
||||
sugar:
|
||||
|
||||
```rust,ignore
|
||||
if let pattern = expression {
|
||||
body
|
||||
}
|
||||
|
||||
match expression {
|
||||
pattern => body,
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
And in fact, we can include an `else` and it becomes the body of the `_`
|
||||
case:
|
||||
|
||||
```rust,ignore
|
||||
if let pattern = expression {
|
||||
body
|
||||
} else {
|
||||
else_body
|
||||
}
|
||||
|
||||
match expression {
|
||||
pattern => body,
|
||||
_ => else_body,
|
||||
}
|
||||
```
|
||||
|
||||
In other words, it's the high-level construct we were originally looking for:
|
||||
do something with a single pattern.
|
||||
# Patterns
|
||||
|
||||
We've mentioned 'patterns' a few times so far: they're used in `let` bindings,
|
||||
in function arguments, and in the `match` expression. Patterns have a lot of
|
||||
abilities, so in this section, we'll cover all of the different things they can
|
||||
do. Any of these abilities work in any place where a pattern is used.
|
||||
|
||||
## Literals & _
|
||||
|
||||
You can match against literals directly, and `_` acts as an any case:
|
||||
|
||||
```rust
|
||||
let x = 1;
|
||||
|
||||
match x {
|
||||
1 => println!("one"),
|
||||
2 => println!("two"),
|
||||
3 => println!("three"),
|
||||
_ => println!("anything"),
|
||||
}
|
||||
```
|
||||
|
||||
This prints `one`.
|
||||
|
||||
# Multiple patterns
|
||||
|
||||
You can match multiple patterns with `|`:
|
||||
|
||||
```rust
|
||||
let x = 1;
|
||||
|
||||
match x {
|
||||
1 | 2 => println!("one or two"),
|
||||
3 => println!("three"),
|
||||
_ => println!("anything"),
|
||||
}
|
||||
```
|
||||
|
||||
This prints `one or two`.
|
||||
|
||||
## ref and ref mut
|
||||
|
||||
Usually, when you match against a pattern, bindings are bound by value.
|
||||
This means you'll end up moving the value out:
|
||||
|
||||
```rust,ignore
|
||||
let name = Some(String::from("Bors"));
|
||||
|
||||
match name {
|
||||
Some(name) => println!("Found a name: {}", name),
|
||||
None => (),
|
||||
}
|
||||
|
||||
// name is moved here. This line will fail to compile:
|
||||
println!("name is: {:?}", name);
|
||||
```
|
||||
|
||||
If you'd prefer to bind `name` by reference, use the `ref` keyword:
|
||||
|
||||
```rust
|
||||
let name = Some(String::from("Bors"));
|
||||
|
||||
match name {
|
||||
Some(ref name) => println!("Found a name: {}", name),
|
||||
None => (),
|
||||
}
|
||||
|
||||
// name is not moved here; the match only took a reference to its data rather
|
||||
// than moving it. This will work:
|
||||
println!("name is: {:?}", name);
|
||||
```
|
||||
|
||||
And for a mutable reference, `ref mut`:
|
||||
|
||||
```rust
|
||||
let mut name = Some(String::from("Bors"));
|
||||
|
||||
match name {
|
||||
Some(ref mut name) => *name = String::from("Another name"),
|
||||
None => (),
|
||||
}
|
||||
|
||||
// name is not moved here; the match only took a reference to its data rather
|
||||
// than moving it
|
||||
```
|
||||
|
||||
## Destructuring
|
||||
|
||||
Patterns can be used to destructure structs and enums:
|
||||
|
||||
```rust
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
let origin = Point { x: 0, y: 0 };
|
||||
|
||||
let Point { x, y } = origin;
|
||||
```
|
||||
|
||||
This brings an `x` and `y` binding into scope, matching the `x` and `y` of
|
||||
`origin`. While it can be unusual in `let`, this is the same principle of
|
||||
patterns in `match`:
|
||||
|
||||
```rust
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
let origin = Point { x: 0, y: 0 };
|
||||
|
||||
match origin {
|
||||
Point { x, y } => { }, // x and y are bound here
|
||||
}
|
||||
```
|
||||
|
||||
## Shadowing
|
||||
|
||||
As with all bindings, anything bound by a pattern will shadow bindings
|
||||
outside of the binding construct:
|
||||
|
||||
```rust
|
||||
let x = Some(5);
|
||||
|
||||
match x {
|
||||
Some(x) => { }, // x is an i32 here, not an Option<i32>
|
||||
None => (),
|
||||
}
|
||||
```
|
||||
|
||||
## Ignoring bindings
|
||||
|
||||
We discussed using `_` as a whole pattern to ignore it above, but you can
|
||||
also use `_` inside of another pattern to ignore just part of it:
|
||||
|
||||
```rust
|
||||
let x = Some(5);
|
||||
|
||||
match x {
|
||||
Some(_) => println!("got a Some and I don't care what's inside"),
|
||||
None => (),
|
||||
}
|
||||
```
|
||||
|
||||
Or like this:
|
||||
|
||||
```rust
|
||||
let numbers = (2, 4, 8, 16, 32);
|
||||
|
||||
match numbers {
|
||||
(first, _, third, _, fifth) => println!("Some numbers: {}, {}, {}", first, third, fifth),
|
||||
}
|
||||
```
|
||||
|
||||
If you want, you can use `..` to ignore all of the parts you haven't defined:
|
||||
|
||||
```rust
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
}
|
||||
|
||||
let origin = Point { x: 0, y: 0, z: 0 };
|
||||
|
||||
match origin {
|
||||
Point { x, .. } => { }, // y and z are ignored
|
||||
}
|
||||
```
|
||||
|
||||
## Ranges
|
||||
|
||||
You can match a range of values with `...`:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
|
||||
match x {
|
||||
1 ... 5 => println!("one through five"),
|
||||
_ => println!("something else"),
|
||||
}
|
||||
```
|
||||
|
||||
Ranges are usually used with integers or `char`s:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 'c';
|
||||
|
||||
match x {
|
||||
'a' ... 'j' => println!("early ASCII letter"),
|
||||
'k' ... 'z' => println!("late ASCII letter"),
|
||||
_ => println!("something else"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Guards
|
||||
|
||||
You can introduce match guards with `if`:
|
||||
|
||||
```rust
|
||||
let x = Some(5);
|
||||
|
||||
match x {
|
||||
Some(x) if x < 5 => println!("less than five: {}", x),
|
||||
Some(x) => println!("{}", x),
|
||||
None => (),
|
||||
}
|
||||
```
|
||||
|
||||
If youre using if with multiple patterns, the if applies to both sides:
|
||||
|
||||
```rust
|
||||
let x = 4;
|
||||
let y = false;
|
||||
|
||||
match x {
|
||||
4 | 5 if y => println!("yes"),
|
||||
_ => println!("no"),
|
||||
}
|
||||
```
|
||||
|
||||
This prints `no`, because the if applies to the whole of `4 | 5`, and not to only
|
||||
the `5`. In other words, the precedence of if behaves like this:
|
||||
|
||||
```text
|
||||
(4 | 5) if y => ...
|
||||
```
|
||||
|
||||
not this:
|
||||
|
||||
```text
|
||||
4 | (5 if y) => ...
|
||||
```
|
||||
|
||||
## Bindings
|
||||
|
||||
You can bind values to names with `@`:
|
Loading…
Reference in New Issue
Block a user