]>
Commit | Line | Data |
---|---|---|
13cf67c4 XL |
1 | ## Defining an Enum |
2 | ||
3 | Let’s look at a situation we might want to express in code and see why enums | |
4 | are useful and more appropriate than structs in this case. Say we need to work | |
5 | with IP addresses. Currently, two major standards are used for IP addresses: | |
6 | version four and version six. These are the only possibilities for an IP | |
7 | address that our program will come across: we can *enumerate* all possible | |
e74abb32 | 8 | variants, which is where enumeration gets its name. |
13cf67c4 XL |
9 | |
10 | Any IP address can be either a version four or a version six address, but not | |
11 | both at the same time. That property of IP addresses makes the enum data | |
e74abb32 | 12 | structure appropriate, because enum values can only be one of its variants. |
13cf67c4 XL |
13 | Both version four and version six addresses are still fundamentally IP |
14 | addresses, so they should be treated as the same type when the code is handling | |
15 | situations that apply to any kind of IP address. | |
16 | ||
17 | We can express this concept in code by defining an `IpAddrKind` enumeration and | |
e74abb32 XL |
18 | listing the possible kinds an IP address can be, `V4` and `V6`. These are the |
19 | variants of the enum: | |
13cf67c4 XL |
20 | |
21 | ```rust | |
74b04a01 | 22 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:def}} |
13cf67c4 XL |
23 | ``` |
24 | ||
25 | `IpAddrKind` is now a custom data type that we can use elsewhere in our code. | |
26 | ||
27 | ### Enum Values | |
28 | ||
29 | We can create instances of each of the two variants of `IpAddrKind` like this: | |
30 | ||
31 | ```rust | |
74b04a01 | 32 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:instance}} |
13cf67c4 XL |
33 | ``` |
34 | ||
35 | Note that the variants of the enum are namespaced under its identifier, and we | |
36 | use a double colon to separate the two. The reason this is useful is that now | |
37 | both values `IpAddrKind::V4` and `IpAddrKind::V6` are of the same type: | |
38 | `IpAddrKind`. We can then, for instance, define a function that takes any | |
39 | `IpAddrKind`: | |
40 | ||
41 | ```rust | |
74b04a01 | 42 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:fn}} |
13cf67c4 XL |
43 | ``` |
44 | ||
45 | And we can call this function with either variant: | |
46 | ||
47 | ```rust | |
74b04a01 | 48 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:fn_call}} |
13cf67c4 XL |
49 | ``` |
50 | ||
51 | Using enums has even more advantages. Thinking more about our IP address type, | |
52 | at the moment we don’t have a way to store the actual IP address *data*; we | |
53 | only know what *kind* it is. Given that you just learned about structs in | |
69743fb6 | 54 | Chapter 5, you might tackle this problem as shown in Listing 6-1. |
13cf67c4 XL |
55 | |
56 | ```rust | |
74b04a01 | 57 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-01/src/main.rs:here}} |
13cf67c4 XL |
58 | ``` |
59 | ||
60 | <span class="caption">Listing 6-1: Storing the data and `IpAddrKind` variant of | |
61 | an IP address using a `struct`</span> | |
62 | ||
63 | Here, we’ve defined a struct `IpAddr` that has two fields: a `kind` field that | |
64 | is of type `IpAddrKind` (the enum we defined previously) and an `address` field | |
65 | of type `String`. We have two instances of this struct. The first, `home`, has | |
66 | the value `IpAddrKind::V4` as its `kind` with associated address data of | |
67 | `127.0.0.1`. The second instance, `loopback`, has the other variant of | |
68 | `IpAddrKind` as its `kind` value, `V6`, and has address `::1` associated with | |
69 | it. We’ve used a struct to bundle the `kind` and `address` values together, so | |
70 | now the variant is associated with the value. | |
71 | ||
72 | We can represent the same concept in a more concise way using just an enum, | |
73 | rather than an enum inside a struct, by putting data directly into each enum | |
74 | variant. This new definition of the `IpAddr` enum says that both `V4` and `V6` | |
75 | variants will have associated `String` values: | |
76 | ||
77 | ```rust | |
74b04a01 | 78 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-02-enum-with-data/src/main.rs:here}} |
13cf67c4 XL |
79 | ``` |
80 | ||
81 | We attach data to each variant of the enum directly, so there is no need for an | |
82 | extra struct. | |
83 | ||
84 | There’s another advantage to using an enum rather than a struct: each variant | |
85 | can have different types and amounts of associated data. Version four type IP | |
86 | addresses will always have four numeric components that will have values | |
87 | between 0 and 255. If we wanted to store `V4` addresses as four `u8` values but | |
88 | still express `V6` addresses as one `String` value, we wouldn’t be able to with | |
89 | a struct. Enums handle this case with ease: | |
90 | ||
91 | ```rust | |
74b04a01 | 92 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-03-variants-with-different-data/src/main.rs:here}} |
13cf67c4 XL |
93 | ``` |
94 | ||
95 | We’ve shown several different ways to define data structures to store version | |
96 | four and version six IP addresses. However, as it turns out, wanting to store | |
97 | IP addresses and encode which kind they are is so common that [the standard | |
98 | library has a definition we can use!][IpAddr]<!-- ignore --> Let’s look at how | |
99 | the standard library defines `IpAddr`: it has the exact enum and variants that | |
100 | we’ve defined and used, but it embeds the address data inside the variants in | |
101 | the form of two different structs, which are defined differently for each | |
102 | variant: | |
103 | ||
104 | [IpAddr]: ../std/net/enum.IpAddr.html | |
105 | ||
106 | ```rust | |
107 | struct Ipv4Addr { | |
108 | // --snip-- | |
109 | } | |
110 | ||
111 | struct Ipv6Addr { | |
112 | // --snip-- | |
113 | } | |
114 | ||
115 | enum IpAddr { | |
116 | V4(Ipv4Addr), | |
117 | V6(Ipv6Addr), | |
118 | } | |
119 | ``` | |
120 | ||
121 | This code illustrates that you can put any kind of data inside an enum variant: | |
122 | strings, numeric types, or structs, for example. You can even include another | |
123 | enum! Also, standard library types are often not much more complicated than | |
124 | what you might come up with. | |
125 | ||
126 | Note that even though the standard library contains a definition for `IpAddr`, | |
127 | we can still create and use our own definition without conflict because we | |
128 | haven’t brought the standard library’s definition into our scope. We’ll talk | |
129 | more about bringing types into scope in Chapter 7. | |
130 | ||
131 | Let’s look at another example of an enum in Listing 6-2: this one has a wide | |
69743fb6 | 132 | variety of types embedded in its variants. |
13cf67c4 XL |
133 | |
134 | ```rust | |
74b04a01 | 135 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-02/src/main.rs:here}} |
13cf67c4 XL |
136 | ``` |
137 | ||
138 | <span class="caption">Listing 6-2: A `Message` enum whose variants each store | |
139 | different amounts and types of values</span> | |
140 | ||
141 | This enum has four variants with different types: | |
142 | ||
143 | * `Quit` has no data associated with it at all. | |
136023e0 | 144 | * `Move` has named fields like a struct does. |
13cf67c4 XL |
145 | * `Write` includes a single `String`. |
146 | * `ChangeColor` includes three `i32` values. | |
147 | ||
69743fb6 | 148 | Defining an enum with variants such as the ones in Listing 6-2 is similar to |
13cf67c4 XL |
149 | defining different kinds of struct definitions, except the enum doesn’t use the |
150 | `struct` keyword and all the variants are grouped together under the `Message` | |
151 | type. The following structs could hold the same data that the preceding enum | |
152 | variants hold: | |
153 | ||
154 | ```rust | |
74b04a01 | 155 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-04-structs-similar-to-message-enum/src/main.rs:here}} |
13cf67c4 XL |
156 | ``` |
157 | ||
158 | But if we used the different structs, which each have their own type, we | |
159 | couldn’t as easily define a function to take any of these kinds of messages as | |
160 | we could with the `Message` enum defined in Listing 6-2, which is a single type. | |
161 | ||
162 | There is one more similarity between enums and structs: just as we’re able to | |
163 | define methods on structs using `impl`, we’re also able to define methods on | |
164 | enums. Here’s a method named `call` that we could define on our `Message` enum: | |
165 | ||
166 | ```rust | |
74b04a01 | 167 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-05-methods-on-enums/src/main.rs:here}} |
13cf67c4 XL |
168 | ``` |
169 | ||
170 | The body of the method would use `self` to get the value that we called the | |
171 | method on. In this example, we’ve created a variable `m` that has the value | |
172 | `Message::Write(String::from("hello"))`, and that is what `self` will be in the | |
173 | body of the `call` method when `m.call()` runs. | |
174 | ||
175 | Let’s look at another enum in the standard library that is very common and | |
176 | useful: `Option`. | |
177 | ||
178 | ### The `Option` Enum and Its Advantages Over Null Values | |
179 | ||
180 | In the previous section, we looked at how the `IpAddr` enum let us use Rust’s | |
181 | type system to encode more information than just the data into our program. | |
182 | This section explores a case study of `Option`, which is another enum defined | |
183 | by the standard library. The `Option` type is used in many places because it | |
184 | encodes the very common scenario in which a value could be something or it | |
185 | could be nothing. Expressing this concept in terms of the type system means the | |
186 | compiler can check whether you’ve handled all the cases you should be handling; | |
187 | this functionality can prevent bugs that are extremely common in other | |
188 | programming languages. | |
189 | ||
190 | Programming language design is often thought of in terms of which features you | |
191 | include, but the features you exclude are important too. Rust doesn’t have the | |
192 | null feature that many other languages have. *Null* is a value that means there | |
193 | is no value there. In languages with null, variables can always be in one of | |
194 | two states: null or not-null. | |
195 | ||
196 | In his 2009 presentation “Null References: The Billion Dollar Mistake,” Tony | |
197 | Hoare, the inventor of null, has this to say: | |
198 | ||
199 | > I call it my billion-dollar mistake. At that time, I was designing the first | |
200 | > comprehensive type system for references in an object-oriented language. My | |
201 | > goal was to ensure that all use of references should be absolutely safe, with | |
202 | > checking performed automatically by the compiler. But I couldn’t resist the | |
203 | > temptation to put in a null reference, simply because it was so easy to | |
204 | > implement. This has led to innumerable errors, vulnerabilities, and system | |
205 | > crashes, which have probably caused a billion dollars of pain and damage in | |
206 | > the last forty years. | |
207 | ||
208 | The problem with null values is that if you try to use a null value as a | |
209 | not-null value, you’ll get an error of some kind. Because this null or not-null | |
210 | property is pervasive, it’s extremely easy to make this kind of error. | |
211 | ||
212 | However, the concept that null is trying to express is still a useful one: a | |
213 | null is a value that is currently invalid or absent for some reason. | |
214 | ||
215 | The problem isn’t really with the concept but with the particular | |
216 | implementation. As such, Rust does not have nulls, but it does have an enum | |
217 | that can encode the concept of a value being present or absent. This enum is | |
218 | `Option<T>`, and it is [defined by the standard library][option]<!-- ignore --> | |
219 | as follows: | |
220 | ||
221 | [option]: ../std/option/enum.Option.html | |
222 | ||
223 | ```rust | |
224 | enum Option<T> { | |
13cf67c4 | 225 | None, |
136023e0 | 226 | Some(T), |
13cf67c4 XL |
227 | } |
228 | ``` | |
229 | ||
230 | The `Option<T>` enum is so useful that it’s even included in the prelude; you | |
231 | don’t need to bring it into scope explicitly. In addition, so are its variants: | |
232 | you can use `Some` and `None` directly without the `Option::` prefix. The | |
233 | `Option<T>` enum is still just a regular enum, and `Some(T)` and `None` are | |
234 | still variants of type `Option<T>`. | |
235 | ||
236 | The `<T>` syntax is a feature of Rust we haven’t talked about yet. It’s a | |
237 | generic type parameter, and we’ll cover generics in more detail in Chapter 10. | |
238 | For now, all you need to know is that `<T>` means the `Some` variant of the | |
239 | `Option` enum can hold one piece of data of any type. Here are some examples of | |
240 | using `Option` values to hold number types and string types: | |
241 | ||
242 | ```rust | |
74b04a01 | 243 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-06-option-examples/src/main.rs:here}} |
13cf67c4 XL |
244 | ``` |
245 | ||
246 | If we use `None` rather than `Some`, we need to tell Rust what type of | |
247 | `Option<T>` we have, because the compiler can’t infer the type that the `Some` | |
248 | variant will hold by looking only at a `None` value. | |
249 | ||
250 | When we have a `Some` value, we know that a value is present and the value is | |
251 | held within the `Some`. When we have a `None` value, in some sense, it means | |
252 | the same thing as null: we don’t have a valid value. So why is having | |
253 | `Option<T>` any better than having null? | |
254 | ||
255 | In short, because `Option<T>` and `T` (where `T` can be any type) are different | |
256 | types, the compiler won’t let us use an `Option<T>` value as if it were | |
257 | definitely a valid value. For example, this code won’t compile because it’s | |
258 | trying to add an `i8` to an `Option<i8>`: | |
259 | ||
260 | ```rust,ignore,does_not_compile | |
74b04a01 | 261 | {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-07-cant-use-option-directly/src/main.rs:here}} |
13cf67c4 XL |
262 | ``` |
263 | ||
264 | If we run this code, we get an error message like this: | |
265 | ||
f035d41b | 266 | ```console |
74b04a01 | 267 | {{#include ../listings/ch06-enums-and-pattern-matching/no-listing-07-cant-use-option-directly/output.txt}} |
13cf67c4 XL |
268 | ``` |
269 | ||
270 | Intense! In effect, this error message means that Rust doesn’t understand how | |
271 | to add an `i8` and an `Option<i8>`, because they’re different types. When we | |
272 | have a value of a type like `i8` in Rust, the compiler will ensure that we | |
273 | always have a valid value. We can proceed confidently without having to check | |
274 | for null before using that value. Only when we have an `Option<i8>` (or | |
275 | whatever type of value we’re working with) do we have to worry about possibly | |
276 | not having a value, and the compiler will make sure we handle that case before | |
277 | using the value. | |
278 | ||
279 | In other words, you have to convert an `Option<T>` to a `T` before you can | |
280 | perform `T` operations with it. Generally, this helps catch one of the most | |
281 | common issues with null: assuming that something isn’t null when it actually | |
282 | is. | |
283 | ||
284 | Not having to worry about incorrectly assuming a not-null value helps you to be | |
285 | more confident in your code. In order to have a value that can possibly be | |
286 | null, you must explicitly opt in by making the type of that value `Option<T>`. | |
287 | Then, when you use that value, you are required to explicitly handle the case | |
288 | when the value is null. Everywhere that a value has a type that isn’t an | |
289 | `Option<T>`, you *can* safely assume that the value isn’t null. This was a | |
290 | deliberate design decision for Rust to limit null’s pervasiveness and increase | |
291 | the safety of Rust code. | |
292 | ||
293 | So, how do you get the `T` value out of a `Some` variant when you have a value | |
294 | of type `Option<T>` so you can use that value? The `Option<T>` enum has a large | |
295 | number of methods that are useful in a variety of situations; you can check | |
296 | them out in [its documentation][docs]<!-- ignore -->. Becoming familiar with | |
297 | the methods on `Option<T>` will be extremely useful in your journey with Rust. | |
298 | ||
299 | [docs]: ../std/option/enum.Option.html | |
300 | ||
301 | In general, in order to use an `Option<T>` value, you want to have code that | |
302 | will handle each variant. You want some code that will run only when you have a | |
303 | `Some(T)` value, and this code is allowed to use the inner `T`. You want some | |
304 | other code to run if you have a `None` value, and that code doesn’t have a `T` | |
305 | value available. The `match` expression is a control flow construct that does | |
306 | just this when used with enums: it will run different code depending on which | |
307 | variant of the enum it has, and that code can use the data inside the matching | |
308 | value. |