mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-02-02 23:38:41 +08:00
Merge remote-tracking branch 'origin/ch6' into first-third-try
This commit is contained in:
commit
99aba46d24
@ -28,8 +28,8 @@
|
||||
- [Enums](ch06-01-enums.md)
|
||||
- [Option](ch06-02-option.md)
|
||||
- [Match](ch06-03-match.md)
|
||||
- [if let](ch06-04-if-let.md)
|
||||
- [Patterns](ch06-05-patterns.md)
|
||||
- [Patterns](ch06-04-patterns.md)
|
||||
- [if let](ch06-05-if-let.md)
|
||||
|
||||
- [Crates & Modules]()
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
# 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.
|
||||
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:
|
||||
Here’s an example of an enum definition:
|
||||
|
||||
```rust
|
||||
enum IpAddrKind {
|
||||
@ -16,10 +17,10 @@ enum IpAddrKind {
|
||||
```
|
||||
|
||||
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.
|
||||
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:
|
||||
|
||||
@ -28,7 +29,7 @@ We can create values of `IpAddrKind` like this:
|
||||
# V4,
|
||||
# V6,
|
||||
# }
|
||||
|
||||
#
|
||||
let four = IpAddrKind::V4;
|
||||
let six = IpAddrKind::V6;
|
||||
```
|
||||
@ -37,7 +38,7 @@ 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,
|
||||
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:
|
||||
|
||||
@ -64,8 +65,8 @@ let loopback = IpAddr {
|
||||
```
|
||||
|
||||
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:
|
||||
with the value itself. We can represent the same thing in a different way with
|
||||
just an enum:
|
||||
|
||||
```rust
|
||||
enum IpAddr {
|
||||
@ -80,14 +81,14 @@ 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.
|
||||
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
|
||||
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),
|
||||
V4(u8, u8, u8, u8),
|
||||
V6(String),
|
||||
}
|
||||
|
||||
@ -97,8 +98,8 @@ 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:
|
||||
The `IpAddr` enum is [in the standard library][IpAddr], but it embeds two
|
||||
different structs inside of its variants:
|
||||
|
||||
```rust
|
||||
struct Ipv4Addr {
|
||||
@ -133,16 +134,21 @@ enum Message {
|
||||
* `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`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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`.
|
||||
|
@ -1,9 +1,9 @@
|
||||
# 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.
|
||||
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 though of as which features you include,
|
||||
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.
|
||||
@ -30,8 +30,8 @@ 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:
|
||||
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> {
|
||||
@ -40,7 +40,7 @@ enum Option<T> {
|
||||
}
|
||||
```
|
||||
|
||||
This type is [provided by the standard library][option], and is so useful that
|
||||
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::`.
|
||||
@ -67,8 +67,8 @@ 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 x: i8 = 5;
|
||||
let y: Option<i8> = Some(5);
|
||||
|
||||
let sum = x + y;
|
||||
```
|
||||
@ -76,8 +76,8 @@ 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]
|
||||
error: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not
|
||||
satisfied [E0277]
|
||||
|
||||
let sum = x + y;
|
||||
^~~~~
|
||||
@ -85,17 +85,26 @@ 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
|
||||
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.
|
||||
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.
|
||||
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? For that, we need a new feature: `match`.
|
||||
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`.
|
||||
|
@ -1,10 +1,165 @@
|
||||
# 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?
|
||||
compare a value against a series of patterns and then execute code based on
|
||||
how they compare.
|
||||
|
||||
A `match` expression is 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. American coins are, in order of
|
||||
diameter from smallest to largest diameter, dime ($0.10), penny ($0.01), nickel
|
||||
($0.05), and quarter ($0.25). It is indeed strange that the dime is smallest
|
||||
in diameter but not smallest in denomination.
|
||||
|
||||
We can write a function in Rust using a `match` expression 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 {
|
||||
Dime,
|
||||
Penny,
|
||||
Nickel,
|
||||
Quarter,
|
||||
}
|
||||
|
||||
fn value_in_cents(coin: Coin) -> i32 {
|
||||
match coin {
|
||||
Coin::Dime => 10,
|
||||
Coin::Penny => 1,
|
||||
Coin::Nickel => 5,
|
||||
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 {
|
||||
# Dime,
|
||||
# Penny,
|
||||
# Nickel,
|
||||
# Quarter,
|
||||
# }
|
||||
#
|
||||
fn value_in_cents(coin: Coin) -> i32 {
|
||||
match coin {
|
||||
Coin::Dime => 10,
|
||||
Coin::Penny => {
|
||||
println!("Lucky penny!");
|
||||
1
|
||||
},
|
||||
Coin::Nickel => 5,
|
||||
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 {
|
||||
Dime,
|
||||
Penny,
|
||||
Nickel,
|
||||
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 {
|
||||
# Dime,
|
||||
# Penny,
|
||||
# Nickel,
|
||||
# Quarter(UsState),
|
||||
# }
|
||||
#
|
||||
fn value_in_cents(coin: Coin) -> i32 {
|
||||
match coin {
|
||||
Coin::Dime => 10,
|
||||
Coin::Penny => 1,
|
||||
Coin::Nickel => 5,
|
||||
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'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:
|
||||
|
||||
@ -21,27 +176,6 @@ 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:
|
||||
|
||||
@ -55,35 +189,25 @@ Does `Some(5)` match `None`? No, it's the wrong variant. So let's continue.
|
||||
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`.
|
||||
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.
|
||||
|
||||
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:
|
||||
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! And so we return `None`. There's no value to add
|
||||
to.
|
||||
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, binding to the data
|
||||
inside, and then executing code based on it. It's a bit tricky at first, but
|
||||
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.
|
||||
|
||||
@ -100,8 +224,8 @@ fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
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]
|
||||
@ -111,59 +235,23 @@ match x {
|
||||
```
|
||||
|
||||
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!
|
||||
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.
|
||||
|
||||
This analysis isn't perfect, however. This will also error:
|
||||
## The _ placeholder
|
||||
|
||||
```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, `_`:
|
||||
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 {
|
||||
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;
|
||||
let some_u8_value = 0u8;
|
||||
match some_u8_value {
|
||||
1 => println!("one"),
|
||||
3 => println!("three"),
|
||||
@ -174,9 +262,11 @@ match some_u8_value {
|
||||
```
|
||||
|
||||
The `_` pattern will match all the other cases, and `()` will do nothing, it's
|
||||
the unit value.
|
||||
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.
|
||||
|
||||
## 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.
|
||||
As we've just seen, patterns are powerful. They can also get complex, so let's
|
||||
take a whole section to cover all of the things that they can do.
|
||||
|
197
src/ch06-04-patterns.md
Normal file
197
src/ch06-04-patterns.md
Normal file
@ -0,0 +1,197 @@
|
||||
# Patterns
|
||||
|
||||
We've mentioned 'patterns' a few times so far: they're used in `let` bindings,
|
||||
in function arguments, and in `match` expressions. Patterns have a lot of
|
||||
abilities, so in this section, we'll cover some of the most commonly used ones.
|
||||
Any of these abilities work in any place where a pattern is used.
|
||||
|
||||
Let's start with an example that is similar to the last example in the previous
|
||||
section:
|
||||
|
||||
```rust
|
||||
let x = 1;
|
||||
|
||||
match x {
|
||||
1 => println!("one"),
|
||||
3 => println!("three"),
|
||||
5 => println!("five"),
|
||||
7 => println!("seven"),
|
||||
_ => println!("anything else"),
|
||||
}
|
||||
```
|
||||
|
||||
This prints `one`. If we change `x` to have the value 4, this would print
|
||||
`anything else`.
|
||||
|
||||
# Multiple patterns
|
||||
|
||||
What if we wanted to print the same thing for 1, 3, and 5? We could do:
|
||||
|
||||
```rust
|
||||
let x = 1;
|
||||
|
||||
match x {
|
||||
1 => println!("an odd number less than six"),
|
||||
3 => println!("an odd number less than six"),
|
||||
5 => println!("an odd number less than six"),
|
||||
7 => println!("seven"),
|
||||
_ => println!("anything else"),
|
||||
}
|
||||
```
|
||||
|
||||
But that repeats the string "an odd number less than six" multiple times. If we
|
||||
had to change that string, it would be annoying to have to change it in three
|
||||
places to make 1, 3, and 5 still have the same behavior.
|
||||
|
||||
Instead, we could match multiple patterns with `|`:
|
||||
|
||||
```rust
|
||||
let x = 1;
|
||||
|
||||
match x {
|
||||
1 | 3 | 5 => println!("an odd number less than six"),
|
||||
7 => println!("seven"),
|
||||
_ => println!("anything else"),
|
||||
}
|
||||
```
|
||||
|
||||
This match statement has the same functionality as the previous one, but we only
|
||||
had to write the common `println!` once!
|
||||
|
||||
## Ranges
|
||||
|
||||
Another way to have multiple values match the same arm is using a range. If,
|
||||
instead of the above where we treated 1, 3, and 5 the same, we wanted to treat
|
||||
any number from 1 to 5 the same, we could do:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
|
||||
match x {
|
||||
1 ... 5 => println!("one through five"),
|
||||
_ => println!("anything else"),
|
||||
}
|
||||
```
|
||||
|
||||
This prints `one through five`: 5 is included in the range.
|
||||
|
||||
Ranges are usually used with integers or `char`s:
|
||||
|
||||
```rust
|
||||
let x = 'c';
|
||||
|
||||
match x {
|
||||
'a' ... 'j' => println!("early ASCII letter"),
|
||||
'k' ... 'z' => println!("late ASCII letter"),
|
||||
_ => println!("something else"),
|
||||
}
|
||||
```
|
||||
|
||||
This prints `early ASCII letter`.
|
||||
|
||||
## 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 {
|
||||
// name is moved here because of the binding to the `Some` value.
|
||||
Some(inner_name) => println!("Found a name: {}", inner_name),
|
||||
None => (),
|
||||
}
|
||||
|
||||
// This line will fail to compile:
|
||||
println!("name is: {:?}", name);
|
||||
```
|
||||
|
||||
If you'd prefer to bind `name` by reference, use the `ref` keyword in order to
|
||||
borrow the value instead:
|
||||
|
||||
```rust
|
||||
let name = Some(String::from("Bors"));
|
||||
|
||||
match name {
|
||||
// name is not moved here.
|
||||
Some(ref inner_name) => println!("Found a name: {}", inner_name),
|
||||
None => (),
|
||||
}
|
||||
|
||||
// The match only took a reference to its data rather than moving it.
|
||||
// This works:
|
||||
println!("name is: {:?}", name);
|
||||
```
|
||||
|
||||
And for a mutable reference, use `ref mut`:
|
||||
|
||||
```rust
|
||||
let mut name = Some(String::from("Bors"));
|
||||
|
||||
match name {
|
||||
// name is not moved here.
|
||||
Some(ref mut inner_name) => *inner_name = String::from("Another name"),
|
||||
None => (),
|
||||
}
|
||||
|
||||
// The match only took a reference to its data rather than moving it.
|
||||
// This works and prints the new value we gave it in the match statement:
|
||||
println!("name is: {:?}", name);
|
||||
```
|
||||
|
||||
## Ignoring bindings
|
||||
|
||||
We discussed using `_` as a whole pattern to ignore any value, but you can
|
||||
also use `_` inside of another pattern to ignore just part of a value. This
|
||||
usage of `_` will ignore the inner value of any `Some` value that is not a
|
||||
`Some` with a `6` inside:
|
||||
|
||||
```rust
|
||||
let x = Some(5);
|
||||
|
||||
match x {
|
||||
Some(6) => println!("got a Some(6)"),
|
||||
Some(_) => println!("got a Some and I don't care what's inside"),
|
||||
None => (),
|
||||
}
|
||||
```
|
||||
|
||||
It’s worth noting that using `_` never binds to the value, which means that the
|
||||
value will not be moved:
|
||||
|
||||
```rust
|
||||
let name = Some(String::from("Bors"));
|
||||
|
||||
match name {
|
||||
// name is not moved here because the _ does not bind to the `Some` value.
|
||||
Some(_) => println!("Found a name!"),
|
||||
None => (),
|
||||
}
|
||||
|
||||
// This works:
|
||||
println!("name is: {:?}", name);
|
||||
```
|
||||
|
||||
## Guards
|
||||
|
||||
You can introduce "match guards" with `if`. This adds an extra condition that
|
||||
often uses a value that the pattern has bound to:
|
||||
|
||||
```rust
|
||||
let x = Some(5);
|
||||
|
||||
match x {
|
||||
Some(x) if x < 5 => println!("less than five: {}", x),
|
||||
Some(x) => println!("{}", x),
|
||||
None => (),
|
||||
}
|
||||
```
|
||||
|
||||
In this case, we bound the inner value of a `Some` to `x` and then "guarded" the
|
||||
first match arm with an additional condition that `x` must be less than 5. In
|
||||
this case, `Some(5)` does not have an inner value that is less than 5, so this
|
||||
code will just print out `5`.
|
||||
|
||||
Whew! That’s a lot of different ways to match things. Let's cover one more place
|
||||
you can use your newfound knowledge of patterns: `if let`.
|
@ -18,7 +18,7 @@ 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
|
||||
a construct that says "Do something with this one case; I don't care about the
|
||||
others."
|
||||
|
||||
Enter `if let`:
|
||||
@ -63,4 +63,4 @@ match expression {
|
||||
```
|
||||
|
||||
In other words, it's the high-level construct we were originally looking for:
|
||||
do something with a single pattern.
|
||||
do something special with only one pattern.
|
@ -1,241 +0,0 @@
|
||||
# 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