Skip to content

Commit 7884eb8

Browse files
committed
Auto merge of #21860 - mdinger:enum_rewording, r=steveklabnik
Second try to address #21196 . A lot that was removed at the end basically seemed repetitive showing simple variations on the same type. It seems more effective to just show more variants at the beginning instead. If you want to pack values into an example, better to use `i32` or some digit than `String` because you don't need the `to_string()` method. I didn't mention `derive` because: * I can't explain it (only use it) * I don't have a link to a good description (maybe rustbyexample but you probably want links internal) * Giving more detail especially stating that `==` won't work and why should help quite a bit I didn't `make test` or check links but I will if this will be merged. @steveklabnik
2 parents 0b56e9b + a9583d6 commit 7884eb8

File tree

1 file changed

+90
-93
lines changed

1 file changed

+90
-93
lines changed

src/doc/trpl/compound-data-types.md

+90-93
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,62 @@ destructuring `let`, as we discussed previously in 'tuples.' In this case, the
200200
## Enums
201201

202202
Finally, Rust has a "sum type", an *enum*. Enums are an incredibly useful
203-
feature of Rust, and are used throughout the standard library. This is an enum
204-
that is provided by the Rust standard library:
203+
feature of Rust, and are used throughout the standard library. An `enum` is
204+
a type which ties a set of alternates to a specific name. For example, below
205+
we define `Character` to be either a `Digit` or something else. These
206+
can be used via their fully scoped names: `Character::Other` (more about `::`
207+
below).
208+
209+
```rust
210+
enum Character {
211+
Digit(i32),
212+
Other,
213+
}
214+
```
215+
216+
An `enum` variant can be defined as most normal types. Below are some example
217+
types have been listed which also would be allowed in an `enum`.
218+
219+
```rust
220+
struct Empty;
221+
struct Color(i32, i32, i32);
222+
struct Length(i32);
223+
struct Status { Health: i32, Mana: i32, Attack: i32, Defense: i32 }
224+
struct HeightDatabase(Vec<i32>);
225+
```
226+
227+
So you see that depending on the sub-datastructure, the `enum` variant, same as
228+
a struct, may or may not hold data. That is, in `Character`, `Digit` is a name
229+
tied to an `i32` where `Other` is just a name. However, the fact that they are
230+
distinct makes this very useful.
231+
232+
As with structures, enums don't by default have access to operators such as
233+
compare ( `==` and `!=`), binary operations (`*` and `+`), and order
234+
(`<` and `>=`). As such, using the previous `Character` type, the
235+
following code is invalid:
236+
237+
```{rust,ignore}
238+
// These assignments both succeed
239+
let ten = Character::Digit(10);
240+
let four = Character::Digit(4);
241+
242+
// Error: `*` is not implemented for type `Character`
243+
let forty = ten * four;
244+
245+
// Error: `<=` is not implemented for type `Character`
246+
let four_is_smaller = four <= ten;
247+
248+
// Error: `==` is not implemented for type `Character`
249+
let four_equals_ten = four == ten;
250+
```
251+
252+
This may seem rather limiting, particularly equality being invalid; in
253+
many cases however, it's unnecessary. Rust provides the [`match`][match]
254+
keyword, which will be examined in more detail in the next section, which
255+
often allows better and easier branch control than a series of `if`/`else`
256+
statements would. However, for our [game][game] we need the comparisons
257+
to work so we will utilize the `Ordering` `enum` provided by the standard
258+
library which supports such comparisons. It has this form:
205259

206260
```{rust}
207261
enum Ordering {
@@ -211,14 +265,9 @@ enum Ordering {
211265
}
212266
```
213267

214-
An `Ordering` can only be _one_ of `Less`, `Equal`, or `Greater` at any given
215-
time.
216-
217-
Because `Ordering` is provided by the standard library, we can use the `use`
218-
keyword to use it in our code. We'll learn more about `use` later, but it's
219-
used to bring names into scope.
220-
221-
Here's an example of how to use `Ordering`:
268+
Because we did not define `Ordering`, we must import it (from the std
269+
library) with the `use` keyword. Here's an example of how `Ordering` is
270+
used:
222271

223272
```{rust}
224273
use std::cmp::Ordering;
@@ -245,11 +294,10 @@ fn main() {
245294
}
246295
```
247296

248-
There's a symbol here we haven't seen before: the double colon (`::`).
249-
This is used to indicate a namespace. In this case, `Ordering` lives in
250-
the `cmp` submodule of the `std` module. We'll talk more about modules
251-
later in the guide. For now, all you need to know is that you can `use`
252-
things from the standard library if you need them.
297+
The `::` symbol is used to indicate a namespace. In this case, `Ordering` lives
298+
in the `cmp` submodule of the `std` module. We'll talk more about modules later
299+
in the guide. For now, all you need to know is that you can `use` things from
300+
the standard library if you need them.
253301

254302
Okay, let's talk about the actual code in the example. `cmp` is a function that
255303
compares two things, and returns an `Ordering`. We return either
@@ -259,95 +307,44 @@ the two values are less, greater, or equal. Note that each variant of the
259307
`Greater`.
260308

261309
The `ordering` variable has the type `Ordering`, and so contains one of the
262-
three values. We can then do a bunch of `if`/`else` comparisons to check which
263-
one it is. However, repeated `if`/`else` comparisons get quite tedious. Rust
264-
has a feature that not only makes them nicer to read, but also makes sure that
265-
you never miss a case. Before we get to that, though, let's talk about another
266-
kind of enum: one with values.
310+
three values. We then do a bunch of `if`/`else` comparisons to check which
311+
one it is.
267312

268-
This enum has two variants, one of which has a value:
313+
This `Ordering::Greater` notation is too long. Lets use `use` to import can
314+
the `enum` variants instead. This will avoid full scoping:
269315

270316
```{rust}
271-
enum OptionalInt {
272-
Value(i32),
273-
Missing,
274-
}
275-
```
276-
277-
This enum represents an `i32` that we may or may not have. In the `Missing`
278-
case, we have no value, but in the `Value` case, we do. This enum is specific
279-
to `i32`s, though. We can make it usable by any type, but we haven't quite
280-
gotten there yet!
281-
282-
You can also have any number of values in an enum:
317+
use std::cmp::Ordering::{self, Equal, Less, Greater};
283318
284-
```{rust}
285-
enum OptionalColor {
286-
Color(i32, i32, i32),
287-
Missing,
288-
}
289-
```
290-
291-
And you can also have something like this:
292-
293-
```{rust}
294-
enum StringResult {
295-
StringOK(String),
296-
ErrorReason(String),
319+
fn cmp(a: i32, b: i32) -> Ordering {
320+
if a < b { Less }
321+
else if a > b { Greater }
322+
else { Equal }
297323
}
298-
```
299-
Where a `StringResult` is either a `StringResult::StringOK`, with the result of
300-
a computation, or a `StringResult::ErrorReason` with a `String` explaining
301-
what caused the computation to fail. These kinds of `enum`s are actually very
302-
useful and are even part of the standard library.
303324
304-
Here is an example of using our `StringResult`:
325+
fn main() {
326+
let x = 5;
327+
let y = 10;
305328
306-
```rust
307-
enum StringResult {
308-
StringOK(String),
309-
ErrorReason(String),
310-
}
329+
let ordering = cmp(x, y); // ordering: Ordering
311330
312-
fn respond(greeting: &str) -> StringResult {
313-
if greeting == "Hello" {
314-
StringResult::StringOK("Good morning!".to_string())
315-
} else {
316-
StringResult::ErrorReason("I didn't understand you!".to_string())
317-
}
331+
if ordering == Less { println!("less"); }
332+
else if ordering == Greater { println!("greater"); }
333+
else if ordering == Equal { println!("equal"); }
318334
}
319335
```
320336

321-
That's a lot of typing! We can use the `use` keyword to make it shorter:
337+
Importing variants is convenient and compact, but can also cause name conflicts,
338+
so do this with caution. It's considered good style to rarely import variants
339+
for this reason.
322340

323-
```rust
324-
use StringResult::StringOK;
325-
use StringResult::ErrorReason;
341+
As you can see, `enum`s are quite a powerful tool for data representation, and are
342+
even more useful when they're [generic][generics] across types. Before we
343+
get to generics, though, let's talk about how to use them with pattern matching, a
344+
tool that will let us deconstruct this sum type (the type theory term for enums)
345+
in a very elegant way and avoid all these messy `if`/`else`s.
326346

327-
enum StringResult {
328-
StringOK(String),
329-
ErrorReason(String),
330-
}
331-
332-
# fn main() {}
333-
334-
fn respond(greeting: &str) -> StringResult {
335-
if greeting == "Hello" {
336-
StringOK("Good morning!".to_string())
337-
} else {
338-
ErrorReason("I didn't understand you!".to_string())
339-
}
340-
}
341-
```
342347

343-
`use` declarations must come before anything else, which looks a little strange in this example,
344-
since we `use` the variants before we define them. Anyway, in the body of `respond`, we can just
345-
say `StringOK` now, rather than the full `StringResult::StringOK`. Importing variants can be
346-
convenient, but can also cause name conflicts, so do this with caution. It's considered good style
347-
to rarely import variants for this reason.
348-
349-
As you can see, `enum`s with values are quite a powerful tool for data representation,
350-
and can be even more useful when they're generic across types. Before we get to generics,
351-
though, let's talk about how to use them with pattern matching, a tool that will
352-
let us deconstruct this sum type (the type theory term for enums) in a very elegant
353-
way and avoid all these messy `if`/`else`s.
348+
[match]: ./match.html
349+
[game]: ./guessing-game.html#comparing-guesses
350+
[generics]: ./generics.html

0 commit comments

Comments
 (0)