mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-02-02 23:38:41 +08:00
First bit of sub-chapters of enums
This commit is contained in:
parent
54d3dc1059
commit
f23e4b3d5a
@ -27,10 +27,10 @@
|
||||
- [Advanced]()
|
||||
|
||||
- [Enums](enums.md)
|
||||
- [Match]()
|
||||
- [Patterns]()
|
||||
- [Option]()
|
||||
- [if let]()
|
||||
- [Option](option.md)
|
||||
- [Match](match.md)
|
||||
- [if let](if-let.md)
|
||||
- [Patterns](patterns.md)
|
||||
|
||||
- [Crates & Modules]()
|
||||
|
||||
|
174
src/match.md
Normal file
174
src/match.md
Normal file
@ -0,0 +1,174 @@
|
||||
# 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, it executes the associated code.
|
||||
If it doesn't match, it 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"),
|
||||
}
|
||||
```
|
||||
|
||||
Even though a `u8` can only have valid values of zero through seven, 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"),
|
||||
_ => 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.
|
99
src/option.md
Normal file
99
src/option.md
Normal file
@ -0,0 +1,99 @@
|
||||
# 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, Tony Hoare, 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.
|
||||
|
||||
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 in 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`.
|
Loading…
Reference in New Issue
Block a user