mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-01-23 07:20:20 +08:00
Rearrange sections to match the structure change made in the docx
This also makes chapter 6 more like the other chapters, with the intro page pretty short.
This commit is contained in:
parent
d1aa4d806e
commit
4888172a74
@ -24,7 +24,7 @@
|
||||
- [Method Syntax](ch05-01-method-syntax.md)
|
||||
|
||||
- [Enums](ch06-00-enums.md)
|
||||
- [Option](ch06-01-option.md)
|
||||
- [Defining an Enum](ch06-01-defining-an-enum.md)
|
||||
- [Match](ch06-02-match.md)
|
||||
- [`if let`](ch06-03-if-let.md)
|
||||
|
||||
|
@ -13,276 +13,3 @@ code.
|
||||
Enums are a feature in many languages, but their capabilities differ
|
||||
per-language. Rust’s enums are most similar to "algebraic data types" in
|
||||
functional languages like F#, OCaml, or Haskell.
|
||||
|
||||
## Defining an Enum
|
||||
|
||||
Let's look at a situation we might want to express in code and see why enums are
|
||||
sometimes a more appropriate choice than structs for modeling data. Say we need
|
||||
to work with IP addresses. There are two major standards used for IP addresses
|
||||
today: version four and version six. These are the only possibilities for an IP
|
||||
address that our program will come across: we can *enumerate* all possible
|
||||
values, which is where *enumeration* gets its name.
|
||||
|
||||
Any IP address can be either a version four or a version six address, but not
|
||||
both at the same time. That property of IP addresses makes the enum data
|
||||
structure appropriate for this case, since enum values can only be one of the
|
||||
variants. Both version four and version six addresses are still fundamentally
|
||||
IP addresses, though, so they should be treated as the same type when the code
|
||||
is handling situations that apply to any kind of IP address.
|
||||
|
||||
We can express this concept in code by defining an `IpAddrKind` enumeration and
|
||||
listing the possible kinds an IP address can be, `V4` and `V6`. These are known
|
||||
as the *variants* of the enum:
|
||||
|
||||
```rust
|
||||
enum IpAddrKind {
|
||||
V4,
|
||||
V6,
|
||||
}
|
||||
```
|
||||
|
||||
This is now a custom data type that we can use elsewhere in our code.
|
||||
|
||||
### Enum Values
|
||||
|
||||
<!-- Option 1: -->
|
||||
An `enum` definition is similar to a `struct` definition: it defines a new type
|
||||
and a template of what instances of that new type will be like. When you want to
|
||||
use a struct, you create an instance of the struct. When you want to use an
|
||||
enum, you create an instance of the enum that is one of the variants the enum
|
||||
allows.
|
||||
|
||||
<!-- Option 2: -->
|
||||
When you want to use a struct, you create an instance of the *struct* itself.
|
||||
When you want to use an enum, you create an instance of one of its *variants*.
|
||||
Each variant is defined like a struct, and you instantiate both using the same
|
||||
syntax.
|
||||
|
||||
<!-- end options -->
|
||||
|
||||
We can create instances of each of the two variants 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 identifier, and we
|
||||
use the double colon to separate the two. The reason this is useful is that now
|
||||
both values `IpAddrKind::V4` and `IpAddrKind::V6` are of the same type:
|
||||
`IpAddrKind`. We can then, for instance, define a function that takes any
|
||||
`IpAddrKind`:
|
||||
|
||||
```rust
|
||||
# enum IpAddrKind {
|
||||
# V4,
|
||||
# V6,
|
||||
# }
|
||||
#
|
||||
fn route(ip_type: IpAddrKind) { }
|
||||
```
|
||||
|
||||
And we can call this function with either variant:
|
||||
|
||||
```rust
|
||||
# enum IpAddrKind {
|
||||
# V4,
|
||||
# V6,
|
||||
# }
|
||||
#
|
||||
# fn route(ip_type: IpAddrKind) { }
|
||||
#
|
||||
route(IpAddrKind::V4);
|
||||
route(IpAddrKind::V6);
|
||||
```
|
||||
|
||||
Enums have more tricks up their sleeves, too. Thinking more about our IP
|
||||
address type, at the moment 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 as in Listing 6-1:
|
||||
|
||||
<figure>
|
||||
|
||||
```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"),
|
||||
};
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 6-1: Storing the data and type of an IP address using a `struct`
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Here, we've defined a struct `IpAddr` that has two fields: a `kind` field that
|
||||
is of type `IpAddrKind` (the enum we defined previously), and an `address`
|
||||
field of type `String`. We have two instances of this struct. The first,
|
||||
`home`, has the value `IpAddrKind::V4` as its `kind`, with associated address
|
||||
data of `127.0.0.1`. The second instance, `loopback`, has the other variant of
|
||||
`IpAddrKind` as its `kind` value, `V6`, and has address `::1` associated with
|
||||
it. We’ve used a struct to bundle the `kind` and `address` values together, so
|
||||
that now the kind is associated with the value itself.
|
||||
|
||||
We can represent the same concept in a more concise way using just an enum,
|
||||
rather than an enum inside a struct, by putting data directly into each enum
|
||||
variant. This new definition of the `IpAddr` enum says that both `V4` and `V6`
|
||||
variants will have associated `String` values:
|
||||
|
||||
```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 attach data to each variant of the enum directly, no need for an extra
|
||||
struct.
|
||||
|
||||
There's another advantage to using an enum over a struct: each variant can
|
||||
store *different kinds* of data. Version four type IP addresses will always
|
||||
have four numeric components that will have values between 0 and 255. If we
|
||||
wanted to store `V4` addresses as four `u8`s but still express `V6` addresses
|
||||
as `String`s, we wouldn't be able to with a `struct`. Enums handle 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"));
|
||||
```
|
||||
|
||||
We've been showing a bunch of different possibilities that we could define in
|
||||
our code for storing IP addresses of the two different kinds using an enum. It
|
||||
turns out, though, that wanting to store IP addresses and encode which kind
|
||||
they are is so common that [the standard library has a definition we can
|
||||
use!][IpAddr]<!-- ignore --> Let's look at how the standard library defines
|
||||
`IpAddr`: it has the exact enum and variants that we've defined and used, but
|
||||
it chose to embed the address data inside the variants in the form of two
|
||||
different structs, which are defined differently for each variant:
|
||||
|
||||
[IpAddr]: ../std/net/enum.IpAddr.html
|
||||
|
||||
```rust
|
||||
struct Ipv4Addr {
|
||||
// details elided
|
||||
}
|
||||
|
||||
struct Ipv6Addr {
|
||||
// details elided
|
||||
}
|
||||
|
||||
enum IpAddr {
|
||||
V4(Ipv4Addr),
|
||||
V6(Ipv6Addr),
|
||||
}
|
||||
```
|
||||
|
||||
This illustrates you can put any kind of data inside of an enum variant:
|
||||
strings, numeric types, structs, and you could even include another enum! Also,
|
||||
standard library types are often not much more complicated than what you might
|
||||
come up with.
|
||||
|
||||
Note that even though the standard library contains a definition for `IpAddr`,
|
||||
we can still choose to create and use our own definition without conflict since
|
||||
we haven't brought the standard library's definition into our scope. We'll talk
|
||||
more about importing types in Chapter 7.
|
||||
|
||||
Let's look at another example: here’s an enum with a wide variety of types
|
||||
embedded in its variants:
|
||||
|
||||
```rust
|
||||
enum Message {
|
||||
Quit,
|
||||
Move { x: i32, y: i32 },
|
||||
Write(String),
|
||||
ChangeColor(i32, i32, i32),
|
||||
}
|
||||
```
|
||||
|
||||
This enum has four variants with different types:
|
||||
|
||||
* `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 is similar to different kinds of struct definitions, except without the
|
||||
`struct` keyword and all grouped together under the `Message` type. The
|
||||
following structs could hold the same data that the enum variants above hold:
|
||||
|
||||
```rust
|
||||
struct QuitMessage; // unit struct
|
||||
struct MoveMessage {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
struct WriteMessage(String); // tuple struct
|
||||
struct ChangeColorMessage(i32, i32, i32); // tuple struct
|
||||
```
|
||||
|
||||
But if we used the different structs, which each have their own type, we
|
||||
wouldn't be able to as easily define a function that could take any of these
|
||||
kinds of messages as we could with the `Message` enum defined above, which is a
|
||||
single type.
|
||||
|
||||
One more similarity between enums and structs: just as we are able to define
|
||||
methods on structs using `impl`, we are also able to define methods on enums.
|
||||
Here's a method, `call`, that we could define on our `Message` enum:
|
||||
|
||||
```rust
|
||||
# enum Message {
|
||||
# Quit,
|
||||
# Move { x: i32, y: i32 },
|
||||
# Write(String),
|
||||
# ChangeColor(i32, i32, i32),
|
||||
# }
|
||||
#
|
||||
impl Message {
|
||||
fn call(&self) {
|
||||
// body would be defined here
|
||||
}
|
||||
}
|
||||
|
||||
let m = Message::Write(String::from("hello"));
|
||||
m.call();
|
||||
```
|
||||
|
||||
The body of the method would use `self` to get the value that we called the
|
||||
method on. In this example, we've created a variable `m` that has the value
|
||||
`Message::Write("hello")`, and that is what `self` will be in the body of
|
||||
the `call` method when `m.call()` runs.
|
||||
|
||||
Let's look at another enum in the standard library that is very common and
|
||||
useful: `Option`.
|
||||
|
415
src/ch06-01-defining-an-enum.md
Normal file
415
src/ch06-01-defining-an-enum.md
Normal file
@ -0,0 +1,415 @@
|
||||
|
||||
## Defining an Enum
|
||||
|
||||
Let's look at a situation we might want to express in code and see why enums are
|
||||
sometimes a more appropriate choice than structs for modeling data. Say we need
|
||||
to work with IP addresses. There are two major standards used for IP addresses
|
||||
today: version four and version six. These are the only possibilities for an IP
|
||||
address that our program will come across: we can *enumerate* all possible
|
||||
values, which is where *enumeration* gets its name.
|
||||
|
||||
Any IP address can be either a version four or a version six address, but not
|
||||
both at the same time. That property of IP addresses makes the enum data
|
||||
structure appropriate for this case, since enum values can only be one of the
|
||||
variants. Both version four and version six addresses are still fundamentally
|
||||
IP addresses, though, so they should be treated as the same type when the code
|
||||
is handling situations that apply to any kind of IP address.
|
||||
|
||||
We can express this concept in code by defining an `IpAddrKind` enumeration and
|
||||
listing the possible kinds an IP address can be, `V4` and `V6`. These are known
|
||||
as the *variants* of the enum:
|
||||
|
||||
```rust
|
||||
enum IpAddrKind {
|
||||
V4,
|
||||
V6,
|
||||
}
|
||||
```
|
||||
|
||||
This is now a custom data type that we can use elsewhere in our code.
|
||||
|
||||
### Enum Values
|
||||
|
||||
<!-- Option 1: -->
|
||||
An `enum` definition is similar to a `struct` definition: it defines a new type
|
||||
and a template of what instances of that new type will be like. When you want to
|
||||
use a struct, you create an instance of the struct. When you want to use an
|
||||
enum, you create an instance of the enum that is one of the variants the enum
|
||||
allows.
|
||||
|
||||
<!-- Option 2: -->
|
||||
When you want to use a struct, you create an instance of the *struct* itself.
|
||||
When you want to use an enum, you create an instance of one of its *variants*.
|
||||
Each variant is defined like a struct, and you instantiate both using the same
|
||||
syntax.
|
||||
|
||||
<!-- end options -->
|
||||
|
||||
We can create instances of each of the two variants 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 identifier, and we
|
||||
use the double colon to separate the two. The reason this is useful is that now
|
||||
both values `IpAddrKind::V4` and `IpAddrKind::V6` are of the same type:
|
||||
`IpAddrKind`. We can then, for instance, define a function that takes any
|
||||
`IpAddrKind`:
|
||||
|
||||
```rust
|
||||
# enum IpAddrKind {
|
||||
# V4,
|
||||
# V6,
|
||||
# }
|
||||
#
|
||||
fn route(ip_type: IpAddrKind) { }
|
||||
```
|
||||
|
||||
And we can call this function with either variant:
|
||||
|
||||
```rust
|
||||
# enum IpAddrKind {
|
||||
# V4,
|
||||
# V6,
|
||||
# }
|
||||
#
|
||||
# fn route(ip_type: IpAddrKind) { }
|
||||
#
|
||||
route(IpAddrKind::V4);
|
||||
route(IpAddrKind::V6);
|
||||
```
|
||||
|
||||
Enums have more tricks up their sleeves, too. Thinking more about our IP
|
||||
address type, at the moment 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 as in Listing 6-1:
|
||||
|
||||
<figure>
|
||||
|
||||
```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"),
|
||||
};
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 6-1: Storing the data and type of an IP address using a `struct`
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Here, we've defined a struct `IpAddr` that has two fields: a `kind` field that
|
||||
is of type `IpAddrKind` (the enum we defined previously), and an `address`
|
||||
field of type `String`. We have two instances of this struct. The first,
|
||||
`home`, has the value `IpAddrKind::V4` as its `kind`, with associated address
|
||||
data of `127.0.0.1`. The second instance, `loopback`, has the other variant of
|
||||
`IpAddrKind` as its `kind` value, `V6`, and has address `::1` associated with
|
||||
it. We’ve used a struct to bundle the `kind` and `address` values together, so
|
||||
that now the kind is associated with the value itself.
|
||||
|
||||
We can represent the same concept in a more concise way using just an enum,
|
||||
rather than an enum inside a struct, by putting data directly into each enum
|
||||
variant. This new definition of the `IpAddr` enum says that both `V4` and `V6`
|
||||
variants will have associated `String` values:
|
||||
|
||||
```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 attach data to each variant of the enum directly, no need for an extra
|
||||
struct.
|
||||
|
||||
There's another advantage to using an enum over a struct: each variant can
|
||||
store *different kinds* of data. Version four type IP addresses will always
|
||||
have four numeric components that will have values between 0 and 255. If we
|
||||
wanted to store `V4` addresses as four `u8`s but still express `V6` addresses
|
||||
as `String`s, we wouldn't be able to with a `struct`. Enums handle 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"));
|
||||
```
|
||||
|
||||
We've been showing a bunch of different possibilities that we could define in
|
||||
our code for storing IP addresses of the two different kinds using an enum. It
|
||||
turns out, though, that wanting to store IP addresses and encode which kind
|
||||
they are is so common that [the standard library has a definition we can
|
||||
use!][IpAddr]<!-- ignore --> Let's look at how the standard library defines
|
||||
`IpAddr`: it has the exact enum and variants that we've defined and used, but
|
||||
it chose to embed the address data inside the variants in the form of two
|
||||
different structs, which are defined differently for each variant:
|
||||
|
||||
[IpAddr]: ../std/net/enum.IpAddr.html
|
||||
|
||||
```rust
|
||||
struct Ipv4Addr {
|
||||
// details elided
|
||||
}
|
||||
|
||||
struct Ipv6Addr {
|
||||
// details elided
|
||||
}
|
||||
|
||||
enum IpAddr {
|
||||
V4(Ipv4Addr),
|
||||
V6(Ipv6Addr),
|
||||
}
|
||||
```
|
||||
|
||||
This illustrates you can put any kind of data inside of an enum variant:
|
||||
strings, numeric types, structs, and you could even include another enum! Also,
|
||||
standard library types are often not much more complicated than what you might
|
||||
come up with.
|
||||
|
||||
Note that even though the standard library contains a definition for `IpAddr`,
|
||||
we can still choose to create and use our own definition without conflict since
|
||||
we haven't brought the standard library's definition into our scope. We'll talk
|
||||
more about importing types in Chapter 7.
|
||||
|
||||
Let's look at another example: here’s an enum with a wide variety of types
|
||||
embedded in its variants:
|
||||
|
||||
```rust
|
||||
enum Message {
|
||||
Quit,
|
||||
Move { x: i32, y: i32 },
|
||||
Write(String),
|
||||
ChangeColor(i32, i32, i32),
|
||||
}
|
||||
```
|
||||
|
||||
This enum has four variants with different types:
|
||||
|
||||
* `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 is similar to different kinds of struct definitions, except without the
|
||||
`struct` keyword and all grouped together under the `Message` type. The
|
||||
following structs could hold the same data that the enum variants above hold:
|
||||
|
||||
```rust
|
||||
struct QuitMessage; // unit struct
|
||||
struct MoveMessage {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
struct WriteMessage(String); // tuple struct
|
||||
struct ChangeColorMessage(i32, i32, i32); // tuple struct
|
||||
```
|
||||
|
||||
But if we used the different structs, which each have their own type, we
|
||||
wouldn't be able to as easily define a function that could take any of these
|
||||
kinds of messages as we could with the `Message` enum defined above, which is a
|
||||
single type.
|
||||
|
||||
One more similarity between enums and structs: just as we are able to define
|
||||
methods on structs using `impl`, we are also able to define methods on enums.
|
||||
Here's a method, `call`, that we could define on our `Message` enum:
|
||||
|
||||
```rust
|
||||
# enum Message {
|
||||
# Quit,
|
||||
# Move { x: i32, y: i32 },
|
||||
# Write(String),
|
||||
# ChangeColor(i32, i32, i32),
|
||||
# }
|
||||
#
|
||||
impl Message {
|
||||
fn call(&self) {
|
||||
// body would be defined here
|
||||
}
|
||||
}
|
||||
|
||||
let m = Message::Write(String::from("hello"));
|
||||
m.call();
|
||||
```
|
||||
|
||||
The body of the method would use `self` to get the value that we called the
|
||||
method on. In this example, we've created a variable `m` that has the value
|
||||
`Message::Write("hello")`, and that is what `self` will be in the body of
|
||||
the `call` method when `m.call()` runs.
|
||||
|
||||
Let's look at another enum in the standard library that is very common and
|
||||
useful: `Option`.
|
||||
|
||||
## The `Option` Enum and its Advantages Over Null Values
|
||||
|
||||
In the previous section, we looked at how the `IpAddr` enum let us use Rust's
|
||||
type system to encode more information than just the data into our program.
|
||||
This section is a case study of `Option`, which is another enum defined by the
|
||||
standard library. The `Option` type is used in many places because it encodes
|
||||
the very common scenario in which a value could be *something* or it could be
|
||||
*nothing*. Expressing this concept in terms of the type system means the
|
||||
compiler can check that you've handled all the cases you should be handling,
|
||||
which can prevent bugs that are extremely common in other programming languages.
|
||||
|
||||
Programming language design is often thought of in terms of which features you
|
||||
include, but the features you leave out are important too. Rust does not have
|
||||
the *null* feature that many other languages have. Null is a value that means
|
||||
there is no value there. In languages with null, variables can always be in one
|
||||
of two states: null or not-null.
|
||||
|
||||
The inventor of null 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 that if you try to actually use a value that's
|
||||
null as if it is a not-null value, you'll get an error of some kind. Because
|
||||
this null or not-null property is pervasive, it's extremely easy to make this
|
||||
kind of error.
|
||||
|
||||
The concept that null is trying to express is still a useful one, however: a
|
||||
null is a value which is currently invalid or absent for some reason.
|
||||
|
||||
The problem isn't with the concept itself, but with the particular
|
||||
implementation. As such, Rust does not have nulls, but it does have an enum
|
||||
that can encode the concept of a value being present or absent. This enum is
|
||||
`Option<T>`, and it is [defined by the standard library][option]<!-- ignore -->
|
||||
as follows:
|
||||
|
||||
[option]: ../std/option/enum.Option.html
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None,
|
||||
}
|
||||
```
|
||||
|
||||
The `Option<T>` enum is so useful that it's even included in the prelude; you
|
||||
don't need to import it explicitly. Furthermore, so are its variants: you can
|
||||
use `Some` and `None` directly, without prefixing them with `Option::`.
|
||||
`Option<T>` is still just a regular enum, however, and `Some(T)` and `None` are
|
||||
still values of type `Option<T>`.
|
||||
|
||||
The `<T>` syntax is a feature of Rust we haven't talked about yet. It's a
|
||||
generic type parameter, and we'll cover generics in more detail in Chapter 10.
|
||||
For now, all you need to know is that this means the `Some` variant of the
|
||||
`Option` enum can hold one piece of data of any type. Here are some examples of
|
||||
using `Option` values to hold number types and string types:
|
||||
|
||||
```rust
|
||||
let some_number = Some(5);
|
||||
let some_string = Some("a string");
|
||||
|
||||
let absent_number: Option<i32> = None;
|
||||
```
|
||||
|
||||
If we use `None` rather than `Some`, we need to tell Rust what type of
|
||||
`Option<T>` we have, because the compiler can't infer the type that the `Some`
|
||||
variant will hold by looking at the `None` variant.
|
||||
|
||||
When we have a `Some` value, we know that there is a value present, and the
|
||||
value is held within the `Some`. When we have a `None` value, in some sense,
|
||||
that means the same thing that null does: we do not have a valid value. So why
|
||||
is this any better than null?
|
||||
|
||||
In short, because `Option<T>` and `T` (where `T` can be any type) are different
|
||||
types from each other, so the compiler won't let us use an `Option` value as if
|
||||
it was definitely a valid value. For example, this code won't compile because
|
||||
it's trying to compare an `Option<i8>` to an `i8`:
|
||||
|
||||
```rust,ignore
|
||||
let x: i8 = 5;
|
||||
let y: Option<i8> = Some(5);
|
||||
|
||||
let sum = x + y;
|
||||
```
|
||||
|
||||
If we run this code, we get an error message like this:
|
||||
|
||||
```text
|
||||
error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not satisfied
|
||||
-->
|
||||
|
|
||||
7 | let sum = x + y;
|
||||
| ^^^^^
|
||||
|
|
||||
```
|
||||
|
||||
Intense! What this error message is trying to say is that Rust does not
|
||||
understand how to add an `Option<i8>` and an `i8`, since they're different
|
||||
types. When we have a value of a type like `i8` in Rust, the compiler will
|
||||
ensure that we always have a valid value. We can proceed confidently without
|
||||
having to check for null before using that value. Only when we have an
|
||||
`Option<i8>` (or whatever type of value we're working with) do we have to
|
||||
worry about possibly not having a value, and the compiler will make sure we
|
||||
handle that case before using the value.
|
||||
|
||||
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 `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 the `T` value out of a `Some` variant when you have a
|
||||
value of type `Option<T>` so that you can use that value? The `Option<T>` enum
|
||||
has a large number of methods useful in a variety of situations that you can
|
||||
check out in [its documentation][docs]<!-- ignore -->, and becoming familiar
|
||||
with them will be extremely useful in your journey with Rust.
|
||||
|
||||
[docs]: ../std/option/enum.Option.html
|
||||
|
||||
What we generally want to do in order to use an `Option<T>` value is to have
|
||||
code that will handle each variant. We want some code that will run only in the
|
||||
case that we have a `Some(T)` value, and this code *is* allowed to use the
|
||||
inner `T`. We want some *other* code to run if we have a `None` value, and that
|
||||
code doesn't have a `T` value available. The `match` expression is a control
|
||||
flow construct that does just this, when used with enums: it will run different
|
||||
code depending on which variant of the enum it has, and that code can use the
|
||||
data inside the matching value.
|
@ -1,141 +0,0 @@
|
||||
## The `Option` Enum and its Advantages Over Null Values
|
||||
|
||||
In the previous section, we looked at how the `IpAddr` enum let us use Rust's
|
||||
type system to encode more information than just the data into our program.
|
||||
This section is a case study of `Option`, which is another enum defined by the
|
||||
standard library. The `Option` type is used in many places because it encodes
|
||||
the very common scenario in which a value could be *something* or it could be
|
||||
*nothing*. Expressing this concept in terms of the type system means the
|
||||
compiler can check that you've handled all the cases you should be handling,
|
||||
which can prevent bugs that are extremely common in other programming languages.
|
||||
|
||||
Programming language design is often thought of in terms of which features you
|
||||
include, but the features you leave out are important too. Rust does not have
|
||||
the *null* feature that many other languages have. Null is a value that means
|
||||
there is no value there. In languages with null, variables can always be in one
|
||||
of two states: null or not-null.
|
||||
|
||||
The inventor of null 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 that if you try to actually use a value that's
|
||||
null as if it is a not-null value, you'll get an error of some kind. Because
|
||||
this null or not-null property is pervasive, it's extremely easy to make this
|
||||
kind of error.
|
||||
|
||||
The concept that null is trying to express is still a useful one, however: a
|
||||
null is a value which is currently invalid or absent for some reason.
|
||||
|
||||
The problem isn't with the concept itself, but with the particular
|
||||
implementation. As such, Rust does not have nulls, but it does have an enum
|
||||
that can encode the concept of a value being present or absent. This enum is
|
||||
`Option<T>`, and it is [defined by the standard library][option]<!-- ignore -->
|
||||
as follows:
|
||||
|
||||
[option]: ../std/option/enum.Option.html
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None,
|
||||
}
|
||||
```
|
||||
|
||||
The `Option<T>` enum is so useful that it's even included in the prelude; you
|
||||
don't need to import it explicitly. Furthermore, so are its variants: you can
|
||||
use `Some` and `None` directly, without prefixing them with `Option::`.
|
||||
`Option<T>` is still just a regular enum, however, and `Some(T)` and `None` are
|
||||
still values of type `Option<T>`.
|
||||
|
||||
The `<T>` syntax is a feature of Rust we haven't talked about yet. It's a
|
||||
generic type parameter, and we'll cover generics in more detail in Chapter 10.
|
||||
For now, all you need to know is that this means the `Some` variant of the
|
||||
`Option` enum can hold one piece of data of any type. Here are some examples of
|
||||
using `Option` values to hold number types and string types:
|
||||
|
||||
```rust
|
||||
let some_number = Some(5);
|
||||
let some_string = Some("a string");
|
||||
|
||||
let absent_number: Option<i32> = None;
|
||||
```
|
||||
|
||||
If we use `None` rather than `Some`, we need to tell Rust what type of
|
||||
`Option<T>` we have, because the compiler can't infer the type that the `Some`
|
||||
variant will hold by looking at the `None` variant.
|
||||
|
||||
When we have a `Some` value, we know that there is a value present, and the
|
||||
value is held within the `Some`. When we have a `None` value, in some sense,
|
||||
that means the same thing that null does: we do not have a valid value. So why
|
||||
is this any better than null?
|
||||
|
||||
In short, because `Option<T>` and `T` (where `T` can be any type) are different
|
||||
types from each other, so the compiler won't let us use an `Option` value as if
|
||||
it was definitely a valid value. For example, this code won't compile because
|
||||
it's trying to compare an `Option<i8>` to an `i8`:
|
||||
|
||||
```rust,ignore
|
||||
let x: i8 = 5;
|
||||
let y: Option<i8> = Some(5);
|
||||
|
||||
let sum = x + y;
|
||||
```
|
||||
|
||||
If we run this code, we get an error message like this:
|
||||
|
||||
```text
|
||||
error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not satisfied
|
||||
-->
|
||||
|
|
||||
7 | let sum = x + y;
|
||||
| ^^^^^
|
||||
|
|
||||
```
|
||||
|
||||
Intense! What this error message is trying to say is that Rust does not
|
||||
understand how to add an `Option<i8>` and an `i8`, since they're different
|
||||
types. When we have a value of a type like `i8` in Rust, the compiler will
|
||||
ensure that we always have a valid value. We can proceed confidently without
|
||||
having to check for null before using that value. Only when we have an
|
||||
`Option<i8>` (or whatever type of value we're working with) do we have to
|
||||
worry about possibly not having a value, and the compiler will make sure we
|
||||
handle that case before using the value.
|
||||
|
||||
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 `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 the `T` value out of a `Some` variant when you have a
|
||||
value of type `Option<T>` so that you can use that value? The `Option<T>` enum
|
||||
has a large number of methods useful in a variety of situations that you can
|
||||
check out in [its documentation][docs]<!-- ignore -->, and becoming familiar
|
||||
with them will be extremely useful in your journey with Rust.
|
||||
|
||||
[docs]: ../std/option/enum.Option.html
|
||||
|
||||
What we generally want to do in order to use an `Option<T>` value is to have
|
||||
code that will handle each variant. We want some code that will run only in the
|
||||
case that we have a `Some(T)` value, and this code *is* allowed to use the
|
||||
inner `T`. We want some *other* code to run if we have a `None` value, and that
|
||||
code doesn't have a `T` value available. The `match` expression is a control
|
||||
flow construct that does just this, when used with enums: it will run different
|
||||
code depending on which variant of the enum it has, and that code can use the
|
||||
data inside the matching value.
|
Loading…
Reference in New Issue
Block a user