feat: added cleanscript
This commit is contained in:
399
ch06/ch06-01-defining-an-enum.html
Normal file
399
ch06/ch06-01-defining-an-enum.html
Normal file
@@ -0,0 +1,399 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Defining an Enum</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="defining-an-enum"><a class="header" href="#defining-an-enum">Defining an Enum</a></h2>
|
||||
<p>Where structs give you a way of grouping together related fields and data, like
|
||||
a <code>Rectangle</code> with its <code>width</code> and <code>height</code>, enums give you a way of saying a
|
||||
value is one of a possible set of values. For example, we may want to say that
|
||||
<code>Rectangle</code> is one of a set of possible shapes that also includes <code>Circle</code> and
|
||||
<code>Triangle</code>. To do this, Rust allows us to encode these possibilities as an enum.</p>
|
||||
<p>Let’s look at a situation we might want to express in code and see why enums
|
||||
are useful and more appropriate than structs in this case. Say we need to work
|
||||
with IP addresses. Currently, two major standards are used for IP addresses:
|
||||
version four and version six. Because these are the only possibilities for an
|
||||
IP address that our program will come across, we can <em>enumerate</em> all possible
|
||||
variants, which is where enumeration gets its name.</p>
|
||||
<p>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 because an enum value can only be one of its variants.
|
||||
Both version four and version six addresses are still fundamentally IP
|
||||
addresses, so they should be treated as the same type when the code is handling
|
||||
situations that apply to any kind of IP address.</p>
|
||||
<p>We can express this concept in code by defining an <code>IpAddrKind</code> enumeration and
|
||||
listing the possible kinds an IP address can be, <code>V4</code> and <code>V6</code>. These are the
|
||||
variants of the enum:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024">enum IpAddrKind {
|
||||
V4,
|
||||
V6,
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let four = IpAddrKind::V4;
|
||||
</span><span class="boring"> let six = IpAddrKind::V6;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> route(IpAddrKind::V4);
|
||||
</span><span class="boring"> route(IpAddrKind::V6);
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn route(ip_kind: IpAddrKind) {}</span></code></pre>
|
||||
<p><code>IpAddrKind</code> is now a custom data type that we can use elsewhere in our code.</p>
|
||||
<h3 id="enum-values"><a class="header" href="#enum-values">Enum Values</a></h3>
|
||||
<p>We can create instances of each of the two variants of <code>IpAddrKind</code> like this:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">enum IpAddrKind {
|
||||
</span><span class="boring"> V4,
|
||||
</span><span class="boring"> V6,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span> let four = IpAddrKind::V4;
|
||||
let six = IpAddrKind::V6;
|
||||
<span class="boring">
|
||||
</span><span class="boring"> route(IpAddrKind::V4);
|
||||
</span><span class="boring"> route(IpAddrKind::V6);
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn route(ip_kind: IpAddrKind) {}</span></code></pre>
|
||||
<p>Note that the variants of the enum are namespaced under its identifier, and we
|
||||
use a double colon to separate the two. This is useful because now both values
|
||||
<code>IpAddrKind::V4</code> and <code>IpAddrKind::V6</code> are of the same type: <code>IpAddrKind</code>. We
|
||||
can then, for instance, define a function that takes any <code>IpAddrKind</code>:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">enum IpAddrKind {
|
||||
</span><span class="boring"> V4,
|
||||
</span><span class="boring"> V6,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let four = IpAddrKind::V4;
|
||||
</span><span class="boring"> let six = IpAddrKind::V6;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> route(IpAddrKind::V4);
|
||||
</span><span class="boring"> route(IpAddrKind::V6);
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn route(ip_kind: IpAddrKind) {}</code></pre>
|
||||
<p>And we can call this function with either variant:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">enum IpAddrKind {
|
||||
</span><span class="boring"> V4,
|
||||
</span><span class="boring"> V6,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let four = IpAddrKind::V4;
|
||||
</span><span class="boring"> let six = IpAddrKind::V6;
|
||||
</span><span class="boring">
|
||||
</span> route(IpAddrKind::V4);
|
||||
route(IpAddrKind::V6);
|
||||
<span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn route(ip_kind: IpAddrKind) {}</span></code></pre>
|
||||
<p>Using enums has even more advantages. Thinking more about our IP address type,
|
||||
at the moment we don’t have a way to store the actual IP address <em>data</em>; we
|
||||
only know what <em>kind</em> it is. Given that you just learned about structs in
|
||||
Chapter 5, you might be tempted to tackle this problem with structs as shown in
|
||||
Listing 6-1.</p>
|
||||
<figure class="listing" id="listing-6-1">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> 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"),
|
||||
};
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-1">Listing 6-1</a>: Storing the data and <code>IpAddrKind</code> variant of an IP address using a <code>struct</code></figcaption>
|
||||
</figure>
|
||||
<p>Here, we’ve defined a struct <code>IpAddr</code> that has two fields: a <code>kind</code> field that
|
||||
is of type <code>IpAddrKind</code> (the enum we defined previously) and an <code>address</code> field
|
||||
of type <code>String</code>. We have two instances of this struct. The first is <code>home</code>,
|
||||
and it has the value <code>IpAddrKind::V4</code> as its <code>kind</code> with associated address
|
||||
data of <code>127.0.0.1</code>. The second instance is <code>loopback</code>. It has the other
|
||||
variant of <code>IpAddrKind</code> as its <code>kind</code> value, <code>V6</code>, and has address <code>::1</code>
|
||||
associated with it. We’ve used a struct to bundle the <code>kind</code> and <code>address</code>
|
||||
values together, so now the variant is associated with the value.</p>
|
||||
<p>However, representing the same concept using just an enum is more concise:
|
||||
Rather than an enum inside a struct, we can put data directly into each enum
|
||||
variant. This new definition of the <code>IpAddr</code> enum says that both <code>V4</code> and <code>V6</code>
|
||||
variants will have associated <code>String</code> values:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> enum IpAddr {
|
||||
V4(String),
|
||||
V6(String),
|
||||
}
|
||||
|
||||
let home = IpAddr::V4(String::from("127.0.0.1"));
|
||||
|
||||
let loopback = IpAddr::V6(String::from("::1"));
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>We attach data to each variant of the enum directly, so there is no need for an
|
||||
extra struct. Here, it’s also easier to see another detail of how enums work:
|
||||
The name of each enum variant that we define also becomes a function that
|
||||
constructs an instance of the enum. That is, <code>IpAddr::V4()</code> is a function call
|
||||
that takes a <code>String</code> argument and returns an instance of the <code>IpAddr</code> type. We
|
||||
automatically get this constructor function defined as a result of defining the
|
||||
enum.</p>
|
||||
<p>There’s another advantage to using an enum rather than a struct: Each variant
|
||||
can have different types and amounts of associated data. Version four IP
|
||||
addresses will always have four numeric components that will have values
|
||||
between 0 and 255. If we wanted to store <code>V4</code> addresses as four <code>u8</code> values but
|
||||
still express <code>V6</code> addresses as one <code>String</code> value, we wouldn’t be able to with
|
||||
a struct. Enums handle this case with ease:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> enum IpAddr {
|
||||
V4(u8, u8, u8, u8),
|
||||
V6(String),
|
||||
}
|
||||
|
||||
let home = IpAddr::V4(127, 0, 0, 1);
|
||||
|
||||
let loopback = IpAddr::V6(String::from("::1"));
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>We’ve shown several different ways to define data structures to store version
|
||||
four and version six IP addresses. However, as it turns out, wanting to store
|
||||
IP addresses and encode which kind they are is so common that <a href="../std/net/enum.IpAddr.html">the standard
|
||||
library has a definition we can use!</a><!-- ignore --> Let’s look at how
|
||||
the standard library defines <code>IpAddr</code>. It has the exact enum and variants that
|
||||
we’ve defined and used, but it embeds the address data inside the variants in
|
||||
the form of two different structs, which are defined differently for each
|
||||
variant:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>struct Ipv4Addr {
|
||||
// --snip--
|
||||
}
|
||||
|
||||
struct Ipv6Addr {
|
||||
// --snip--
|
||||
}
|
||||
|
||||
enum IpAddr {
|
||||
V4(Ipv4Addr),
|
||||
V6(Ipv6Addr),
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>This code illustrates that you can put any kind of data inside an enum variant:
|
||||
strings, numeric types, or structs, for example. You can even include another
|
||||
enum! Also, standard library types are often not much more complicated than
|
||||
what you might come up with.</p>
|
||||
<p>Note that even though the standard library contains a definition for <code>IpAddr</code>,
|
||||
we can still create and use our own definition without conflict because we
|
||||
haven’t brought the standard library’s definition into our scope. We’ll talk
|
||||
more about bringing types into scope in Chapter 7.</p>
|
||||
<p>Let’s look at another example of an enum in Listing 6-2: This one has a wide
|
||||
variety of types embedded in its variants.</p>
|
||||
<figure class="listing" id="listing-6-2">
|
||||
<pre class="playground"><code class="language-rust edition2024">enum Message {
|
||||
Quit,
|
||||
Move { x: i32, y: i32 },
|
||||
Write(String),
|
||||
ChangeColor(i32, i32, i32),
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-2">Listing 6-2</a>: A <code>Message</code> enum whose variants each store different amounts and types of values</figcaption>
|
||||
</figure>
|
||||
<p>This enum has four variants with different types:</p>
|
||||
<ul>
|
||||
<li><code>Quit</code>: Has no data associated with it at all</li>
|
||||
<li><code>Move</code>: Has named fields, like a struct does</li>
|
||||
<li><code>Write</code>: Includes a single <code>String</code></li>
|
||||
<li><code>ChangeColor</code>: Includes three <code>i32</code> values</li>
|
||||
</ul>
|
||||
<p>Defining an enum with variants such as the ones in Listing 6-2 is similar to
|
||||
defining different kinds of struct definitions, except the enum doesn’t use the
|
||||
<code>struct</code> keyword and all the variants are grouped together under the <code>Message</code>
|
||||
type. The following structs could hold the same data that the preceding enum
|
||||
variants hold:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024">struct QuitMessage; // unit struct
|
||||
struct MoveMessage {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
struct WriteMessage(String); // tuple struct
|
||||
struct ChangeColorMessage(i32, i32, i32); // tuple struct
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {}</span></code></pre>
|
||||
<p>But if we used the different structs, each of which has its own type, we
|
||||
couldn’t as easily define a function to take any of these kinds of messages as
|
||||
we could with the <code>Message</code> enum defined in Listing 6-2, which is a single type.</p>
|
||||
<p>There is one more similarity between enums and structs: Just as we’re able to
|
||||
define methods on structs using <code>impl</code>, we’re also able to define methods on
|
||||
enums. Here’s a method named <code>call</code> that we could define on our <code>Message</code> enum:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span><span class="boring"> enum Message {
|
||||
</span><span class="boring"> Quit,
|
||||
</span><span class="boring"> Move { x: i32, y: i32 },
|
||||
</span><span class="boring"> Write(String),
|
||||
</span><span class="boring"> ChangeColor(i32, i32, i32),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span> impl Message {
|
||||
fn call(&self) {
|
||||
// method body would be defined here
|
||||
}
|
||||
}
|
||||
|
||||
let m = Message::Write(String::from("hello"));
|
||||
m.call();
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>The body of the method would use <code>self</code> to get the value that we called the
|
||||
method on. In this example, we’ve created a variable <code>m</code> that has the value
|
||||
<code>Message::Write(String::from("hello"))</code>, and that is what <code>self</code> will be in the
|
||||
body of the <code>call</code> method when <code>m.call()</code> runs.</p>
|
||||
<p>Let’s look at another enum in the standard library that is very common and
|
||||
useful: <code>Option</code>.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="the-option-enum-and-its-advantages-over-null-values"></a></p>
|
||||
<h3 id="the-option-enum"><a class="header" href="#the-option-enum">The <code>Option</code> Enum</a></h3>
|
||||
<p>This section explores a case study of <code>Option</code>, which is another enum defined
|
||||
by the standard library. The <code>Option</code> type encodes the very common scenario in
|
||||
which a value could be something, or it could be nothing.</p>
|
||||
<p>For example, if you request the first item in a non-empty list, you would get
|
||||
a value. If you request the first item in an empty list, you would get nothing.
|
||||
Expressing this concept in terms of the type system means the compiler can
|
||||
check whether you’ve handled all the cases you should be handling; this
|
||||
functionality can prevent bugs that are extremely common in other programming
|
||||
languages.</p>
|
||||
<p>Programming language design is often thought of in terms of which features you
|
||||
include, but the features you exclude are important too. Rust doesn’t have the
|
||||
null feature that many other languages have. <em>Null</em> 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.</p>
|
||||
<p>In his 2009 presentation “Null References: The Billion Dollar Mistake,” Tony
|
||||
Hoare, the inventor of null, had this to say:</p>
|
||||
<blockquote>
|
||||
<p>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.</p>
|
||||
</blockquote>
|
||||
<p>The problem with null values is that if you try to use a null value as 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.</p>
|
||||
<p>However, the concept that null is trying to express is still a useful one: A
|
||||
null is a value that is currently invalid or absent for some reason.</p>
|
||||
<p>The problem isn’t really with the concept 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
|
||||
<code>Option<T></code>, and it is <a href="../std/option/enum.Option.html">defined by the standard library</a><!-- ignore -->
|
||||
as follows:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>enum Option<T> {
|
||||
None,
|
||||
Some(T),
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>The <code>Option<T></code> enum is so useful that it’s even included in the prelude; you
|
||||
don’t need to bring it into scope explicitly. Its variants are also included in
|
||||
the prelude: You can use <code>Some</code> and <code>None</code> directly without the <code>Option::</code>
|
||||
prefix. The <code>Option<T></code> enum is still just a regular enum, and <code>Some(T)</code> and
|
||||
<code>None</code> are still variants of type <code>Option<T></code>.</p>
|
||||
<p>The <code><T></code> 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 <code><T></code> means that the <code>Some</code> variant of
|
||||
the <code>Option</code> enum can hold one piece of data of any type, and that each
|
||||
concrete type that gets used in place of <code>T</code> makes the overall <code>Option<T></code> type
|
||||
a different type. Here are some examples of using <code>Option</code> values to hold
|
||||
number types and char types:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> let some_number = Some(5);
|
||||
let some_char = Some('e');
|
||||
|
||||
let absent_number: Option<i32> = None;
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>The type of <code>some_number</code> is <code>Option<i32></code>. The type of <code>some_char</code> is
|
||||
<code>Option<char></code>, which is a different type. Rust can infer these types because
|
||||
we’ve specified a value inside the <code>Some</code> variant. For <code>absent_number</code>, Rust
|
||||
requires us to annotate the overall <code>Option</code> type: The compiler can’t infer the
|
||||
type that the corresponding <code>Some</code> variant will hold by looking only at a
|
||||
<code>None</code> value. Here, we tell Rust that we mean for <code>absent_number</code> to be of type
|
||||
<code>Option<i32></code>.</p>
|
||||
<p>When we have a <code>Some</code> value, we know that a value is present, and the value is
|
||||
held within the <code>Some</code>. When we have a <code>None</code> value, in some sense it means the
|
||||
same thing as null: We don’t have a valid value. So, why is having <code>Option<T></code>
|
||||
any better than having null?</p>
|
||||
<p>In short, because <code>Option<T></code> and <code>T</code> (where <code>T</code> can be any type) are different
|
||||
types, the compiler won’t let us use an <code>Option<T></code> value as if it were
|
||||
definitely a valid value. For example, this code won’t compile, because it’s
|
||||
trying to add an <code>i8</code> to an <code>Option<i8></code>:</p>
|
||||
<pre><code class="language-rust ignore does_not_compile"><span class="boring">fn main() {
|
||||
</span> let x: i8 = 5;
|
||||
let y: Option<i8> = Some(5);
|
||||
|
||||
let sum = x + y;
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>If we run this code, we get an error message like this one:</p>
|
||||
<pre><code class="language-console">$ cargo run
|
||||
Compiling enums v0.1.0 (file:///projects/enums)
|
||||
error[E0277]: cannot add `Option<i8>` to `i8`
|
||||
--> src/main.rs:5:17
|
||||
|
|
||||
5 | let sum = x + y;
|
||||
| ^ no implementation for `i8 + Option<i8>`
|
||||
|
|
||||
= help: the trait `Add<Option<i8>>` is not implemented for `i8`
|
||||
= help: the following other types implement trait `Add<Rhs>`:
|
||||
`&i8` implements `Add<i8>`
|
||||
`&i8` implements `Add`
|
||||
`i8` implements `Add<&i8>`
|
||||
`i8` implements `Add`
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
||||
error: could not compile `enums` (bin "enums") due to 1 previous error
|
||||
</code></pre>
|
||||
<p>Intense! In effect, this error message means that Rust doesn’t understand how
|
||||
to add an <code>i8</code> and an <code>Option<i8></code>, because they’re different types. When we
|
||||
have a value of a type like <code>i8</code> 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 <code>Option<i8></code> (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.</p>
|
||||
<p>In other words, you have to convert an <code>Option<T></code> to a <code>T</code> before you can
|
||||
perform <code>T</code> operations with it. Generally, this helps catch one of the most
|
||||
common issues with null: assuming that something isn’t null when it actually is.</p>
|
||||
<p>Eliminating the risk of incorrectly assuming a not-null value helps you be more
|
||||
confident in your code. In order to have a value that can possibly be null, you
|
||||
must explicitly opt in by making the type of that value <code>Option<T></code>. 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 <code>Option<T></code>,
|
||||
you <em>can</em> 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.</p>
|
||||
<p>So how do you get the <code>T</code> value out of a <code>Some</code> variant when you have a value
|
||||
of type <code>Option<T></code> so that you can use that value? The <code>Option<T></code> enum has a
|
||||
large number of methods that are useful in a variety of situations; you can
|
||||
check them out in <a href="../std/option/enum.Option.html">its documentation</a><!-- ignore -->. Becoming familiar
|
||||
with the methods on <code>Option<T></code> will be extremely useful in your journey with
|
||||
Rust.</p>
|
||||
<p>In general, in order to use an <code>Option<T></code> value, you want to have code that
|
||||
will handle each variant. You want some code that will run only when you have a
|
||||
<code>Some(T)</code> value, and this code is allowed to use the inner <code>T</code>. You want some
|
||||
other code to run only if you have a <code>None</code> value, and that code doesn’t have a
|
||||
<code>T</code> value available. The <code>match</code> 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.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user