Rollup merge of #25348 - geofft:trpl-fix-enums, r=steveklabnik

The enums chapter at the moment is ... weird. The examples aren't about enums, they're about structs, and most of the chapter talks about how enums don't support comparison operators by default (which is also true of other compound data types.) I think there was a story here once, but some coherency got lost in refactoring.

There are two preliminary patches here, one to combine the struct and tuple-struct chapters, and one to document unit-like structs, because enum syntax is easier to explain once you have those three. The final patch moves the enum chapter after the struct chapter, and rewrites most of it to talk about enums usefully (including covering matches on enums).

r? @steveklabnik
This commit is contained in:
Steve Klabnik 2015-05-13 00:52:49 -04:00
commit dd11c7f047
5 changed files with 165 additions and 115 deletions

View File

@ -29,9 +29,9 @@
* [References and Borrowing](references-and-borrowing.md)
* [Lifetimes](lifetimes.md)
* [Mutability](mutability.md)
* [Structs](structs.md)
* [Enums](enums.md)
* [Match](match.md)
* [Structs](structs.md)
* [Patterns](patterns.md)
* [Method Syntax](method-syntax.md)
* [Vectors](vectors.md)
@ -45,7 +45,6 @@
* [Universal Function Call Syntax](ufcs.md)
* [Crates and Modules](crates-and-modules.md)
* [`const` and `static`](const-and-static.md)
* [Tuple Structs](tuple-structs.md)
* [Attributes](attributes.md)
* [`type` aliases](type-aliases.md)
* [Casting between types](casting-between-types.md)

View File

@ -1,68 +1,63 @@
% Enums
Rust has a sum type, an `enum`. Enums are an incredibly useful feature of
Rust, and are used throughout the standard library. An `enum` is a type which
relates a set of alternates to a specific name. For example, below we define
`Character` to be either a `Digit` or something else.
An `enum` in Rust is a type that represents data that could be one of
several possible variants:
```rust
enum Character {
Digit(i32),
Other,
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
```
Most types are allowed as the variant components of an `enum`. Here are some
examples:
Each variant can optionally have data associated with it. The syntax for
defining variants resembles the syntaxes used to define structs: you can
have variants with no data (like unit-like structs), variants with named
data, and variants with unnamed data (like tuple structs). Unlike
separate struct definitions, however, an `enum` is a single type. A
value of the enum can match any of the variants. For this reason, an
enum is sometimes called a sum type: the set of possible values of the
enum is the sum of the sets of possible values for each variant.
```rust
struct Empty;
struct Color(i32, i32, i32);
struct Length(i32);
struct Stats { Health: i32, Mana: i32, Attack: i32, Defense: i32 }
struct HeightDatabase(Vec<i32>);
```
You see that, depending on its type, an `enum` variant may or may not hold data.
In `Character`, for instance, `Digit` gives a meaningful name for an `i32`
value, where `Other` is only a name. However, the fact that they represent
distinct categories of `Character` is a very useful property.
The variants of an `enum` by default are not comparable with equality operators
(`==`, `!=`), have no ordering (`<`, `>=`, etc.), and do not support other
binary operations such as `*` and `+`. As such, the following code is invalid
for the example `Character` type:
```rust,ignore
// These assignments both succeed
let ten = Character::Digit(10);
let four = Character::Digit(4);
// Error: `*` is not implemented for type `Character`
let forty = ten * four;
// Error: `<=` is not implemented for type `Character`
let four_is_smaller = four <= ten;
// Error: `==` is not implemented for type `Character`
let four_equals_ten = four == ten;
```
We use the `::` syntax to use the name of each variant: Theyre scoped by the name
We use the `::` syntax to use the name of each variant: theyre scoped by the name
of the `enum` itself. This allows both of these to work:
```rust,ignore
Character::Digit(10);
Hand::Digit;
```rust
# enum Message {
# Move { x: i32, y: i32 },
# }
let x: Message = Message::Move { x: 3, y: 4 };
enum BoardGameTurn {
Move { squares: i32 },
Pass,
}
let y: BoardGameTurn = BoardGameTurn::Move { squares: 1 };
```
Both variants are named `Digit`, but since theyre scoped to the `enum` name,
Both variants are named `Move`, but since theyre scoped to the name of
the enum, they can both be used without conflict.
Not supporting these operations may seem rather limiting, but its a limitation
which we can overcome. There are two ways: by implementing equality ourselves,
or by pattern matching variants with [`match`][match] expressions, which youll
learn in the next section. We dont know enough about Rust to implement
equality yet, but well find out in the [`traits`][traits] section.
A value of an enum type contains information about which variant it is,
in addition to any data associated with that variant. This is sometimes
referred to as a tagged union, since the data includes a tag
indicating what type it is. The compiler uses this information to
enforce that youre accessing the data in the enum safely. For instance,
you cant simply try to destructure a value as if it were one of the
possible variants:
```rust,ignore
fn process_color_change(msg: Message) {
let Message::ChangeColor(r, g, b) = msg; // compile-time error
}
```
Well see how to safely get data out of enums when we learn about the
[`match`][match] and [`if let`][if-let] statements in the next few
chapters.
[match]: match.html
[traits]: traits.html
[if-let]: if-let.html

View File

@ -61,3 +61,40 @@ let number = match x {
```
Sometimes its a nice way of converting something from one type to another.
# Matching on enums
Another important use of the `match` keyword is to process the possible
variants of an enum:
```rust
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
fn quit() { /* ... */ }
fn change_color(r: i32, g: i32, b: i32) { /* ... */ }
fn move_cursor(x: i32, y: i32) { /* ... */ }
fn process_message(msg: Message) {
match msg {
Message::Quit => quit(),
Message::ChangeColor(r, g, b) => change_color(r, g, b),
Message::Move { x: x, y: y } => move_cursor(x, y),
Message::Write(s) => println!("{}", s),
};
}
```
Again, the Rust compiler checks exhaustiveness, so it demands that you
have a match arm for every variant of the enum. If you leave one off, it
will give you a compile-time error unless you use `_`.
Unlike the previous uses of `match`, you cant use the normal `if`
statement to do this. You can use the [`if let`][if-let] statement,
which can be seen as an abbreviated form of `match`.
[if-let][if-let.html]

View File

@ -117,3 +117,82 @@ ones, and it will copy the values you dont specify:
let origin = Point3d { x: 0, y: 0, z: 0 };
let point = Point3d { z: 1, x: 2, .. origin };
```
# Tuple structs
Rust has another data type thats like a hybrid between a [tuple][tuple] and a
struct, called a tuple struct. Tuple structs have a name, but
their fields dont:
```rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
```
[tuple]: primitive-types.html#tuples
These two will not be equal, even if they have the same values:
```rust
# struct Color(i32, i32, i32);
# struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
```
It is almost always better to use a struct than a tuple struct. We would write
`Color` and `Point` like this instead:
```rust
struct Color {
red: i32,
blue: i32,
green: i32,
}
struct Point {
x: i32,
y: i32,
z: i32,
}
```
Now, we have actual names, rather than positions. Good names are important,
and with a struct, we have actual names.
There _is_ one case when a tuple struct is very useful, though, and thats a
tuple struct with only one element. We call this the newtype pattern, because
it allows you to create a new type, distinct from that of its contained value
and expressing its own semantic meaning:
```rust
struct Inches(i32);
let length = Inches(10);
let Inches(integer_length) = length;
println!("length is {} inches", integer_length);
```
As you can see here, you can extract the inner integer type through a
destructuring `let`, just as with regular tuples. In this case, the
`let Inches(integer_length)` assigns `10` to `integer_length`.
# Unit-like structs
You can define a struct with no members at all:
```rust
struct Electron;
```
Such a struct is called unit-like because it resembles the empty
tuple, `()`, sometimes called unit. Like a tuple struct, it defines a
new type.
This is rarely useful on its own (although sometimes it can serve as a
marker type), but in combination with other features, it can become
useful. For instance, a library may ask you to create a structure that
implements a certain [trait][trait] to handle events. If you dont have
any data you need to store in the structure, you can just create a
unit-like struct.

View File

@ -1,60 +0,0 @@
% Tuple Structs
Rust has another data type that's like a hybrid between a [tuple][tuple] and a
[struct][struct], called a tuple struct. Tuple structs have a name, but
their fields dont:
```rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
```
[tuple]: primitive-types.html#tuples
[struct]: structs.html
These two will not be equal, even if they have the same values:
```rust
# struct Color(i32, i32, i32);
# struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
```
It is almost always better to use a struct than a tuple struct. We would write
`Color` and `Point` like this instead:
```rust
struct Color {
red: i32,
blue: i32,
green: i32,
}
struct Point {
x: i32,
y: i32,
z: i32,
}
```
Now, we have actual names, rather than positions. Good names are important,
and with a struct, we have actual names.
There _is_ one case when a tuple struct is very useful, though, and thats a
tuple struct with only one element. We call this the newtype pattern, because
it allows you to create a new type, distinct from that of its contained value
and expressing its own semantic meaning:
```rust
struct Inches(i32);
let length = Inches(10);
let Inches(integer_length) = length;
println!("length is {} inches", integer_length);
```
As you can see here, you can extract the inner integer type through a
destructuring `let`, as we discussed previously in tuples. In this case, the
`let Inches(integer_length)` assigns `10` to `integer_length`.