First bit of sub-chapters of enums

This commit is contained in:
steveklabnik 2016-05-19 16:29:00 -04:00
parent 54d3dc1059
commit f23e4b3d5a
3 changed files with 277 additions and 4 deletions

View File

@ -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
View 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
View 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`.