feat: added cleanscript
This commit is contained in:
19
ch06/ch06-00-enums.html
Normal file
19
ch06/ch06-00-enums.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Enums and Pattern Matching</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="enums-and-pattern-matching"><a class="header" href="#enums-and-pattern-matching">Enums and Pattern Matching</a></h1>
|
||||
<p>In this chapter, we’ll look at enumerations, also referred to as <em>enums</em>.
|
||||
Enums allow you to define a type by enumerating its possible variants. First
|
||||
we’ll define and use an enum to show how an enum can encode meaning along with
|
||||
data. Next, we’ll explore a particularly useful enum, called <code>Option</code>, which
|
||||
expresses that a value can be either something or nothing. Then, we’ll look at
|
||||
how pattern matching in the <code>match</code> expression makes it easy to run different
|
||||
code for different values of an enum. Finally, we’ll cover how the <code>if let</code>
|
||||
construct is another convenient and concise idiom available to handle enums in
|
||||
your code.</p>
|
||||
</body>
|
||||
</html>
|
||||
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>
|
||||
371
ch06/ch06-02-match.html
Normal file
371
ch06/ch06-02-match.html
Normal file
@@ -0,0 +1,371 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>The match Control Flow Construct</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="the-match-control-flow-operator"></a></p>
|
||||
<h2 id="the-match-control-flow-construct"><a class="header" href="#the-match-control-flow-construct">The <code>match</code> Control Flow Construct</a></h2>
|
||||
<p>Rust has an extremely powerful control flow construct called <code>match</code> that
|
||||
allows you to compare a value against a series of patterns and then execute
|
||||
code based on which pattern matches. Patterns can be made up of literal values,
|
||||
variable names, wildcards, and many other things; <a href="../ch19/ch19-00-patterns.html">Chapter
|
||||
19</a><!-- ignore --> covers all the different kinds of patterns
|
||||
and what they do. The power of <code>match</code> comes from the expressiveness of the
|
||||
patterns and the fact that the compiler confirms that all possible cases are
|
||||
handled.</p>
|
||||
<p>Think of a <code>match</code> expression as being like a coin-sorting machine: Coins slide
|
||||
down a track with variously sized holes along it, and each coin falls through
|
||||
the first hole it encounters that it fits into. In the same way, values go
|
||||
through each pattern in a <code>match</code>, and at the first pattern the value “fits,”
|
||||
the value falls into the associated code block to be used during execution.</p>
|
||||
<p>Speaking of coins, let’s use them as an example using <code>match</code>! We can write a
|
||||
function that takes an unknown US coin and, in a similar way as the counting
|
||||
machine, determines which coin it is and returns its value in cents, as shown
|
||||
in Listing 6-3.</p>
|
||||
<figure class="listing" id="listing-6-3">
|
||||
<pre class="playground"><code class="language-rust edition2024">enum Coin {
|
||||
Penny,
|
||||
Nickel,
|
||||
Dime,
|
||||
Quarter,
|
||||
}
|
||||
|
||||
fn value_in_cents(coin: Coin) -> u8 {
|
||||
match coin {
|
||||
Coin::Penny => 1,
|
||||
Coin::Nickel => 5,
|
||||
Coin::Dime => 10,
|
||||
Coin::Quarter => 25,
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-3">Listing 6-3</a>: An enum and a <code>match</code> expression that has the variants of the enum as its patterns</figcaption>
|
||||
</figure>
|
||||
<p>Let’s break down the <code>match</code> in the <code>value_in_cents</code> function. First, we list
|
||||
the <code>match</code> keyword followed by an expression, which in this case is the value
|
||||
<code>coin</code>. This seems very similar to a conditional expression used with <code>if</code>, but
|
||||
there’s a big difference: With <code>if</code>, the condition needs to evaluate to a
|
||||
Boolean value, but here it can be any type. The type of <code>coin</code> in this example
|
||||
is the <code>Coin</code> enum that we defined on the first line.</p>
|
||||
<p>Next are the <code>match</code> arms. An arm has two parts: a pattern and some code. The
|
||||
first arm here has a pattern that is the value <code>Coin::Penny</code> and then the <code>=></code>
|
||||
operator that separates the pattern and the code to run. The code in this case
|
||||
is just the value <code>1</code>. Each arm is separated from the next with a comma.</p>
|
||||
<p>When the <code>match</code> expression executes, it compares the resultant value against
|
||||
the pattern of each arm, in order. If a pattern matches the value, the code
|
||||
associated with that pattern is executed. If that pattern doesn’t match the
|
||||
value, execution continues to the next arm, much as in a coin-sorting machine.
|
||||
We can have as many arms as we need: In Listing 6-3, our <code>match</code> has four arms.</p>
|
||||
<p>The code associated with each arm is an expression, and the resultant value of
|
||||
the expression in the matching arm is the value that gets returned for the
|
||||
entire <code>match</code> expression.</p>
|
||||
<p>We don’t typically use curly brackets if the match arm code is short, as it is
|
||||
in Listing 6-3 where each arm just returns a value. If you want to run multiple
|
||||
lines of code in a match arm, you must use curly brackets, and the comma
|
||||
following the arm is then optional. For example, the following code prints
|
||||
“Lucky penny!” every time the method is called with a <code>Coin::Penny</code>, but it
|
||||
still returns the last value of the block, <code>1</code>:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">enum Coin {
|
||||
</span><span class="boring"> Penny,
|
||||
</span><span class="boring"> Nickel,
|
||||
</span><span class="boring"> Dime,
|
||||
</span><span class="boring"> Quarter,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn value_in_cents(coin: Coin) -> u8 {
|
||||
match coin {
|
||||
Coin::Penny => {
|
||||
println!("Lucky penny!");
|
||||
1
|
||||
}
|
||||
Coin::Nickel => 5,
|
||||
Coin::Dime => 10,
|
||||
Coin::Quarter => 25,
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {}</span></code></pre>
|
||||
<h3 id="patterns-that-bind-to-values"><a class="header" href="#patterns-that-bind-to-values">Patterns That Bind to Values</a></h3>
|
||||
<p>Another useful feature of match arms is that they can bind to the parts of the
|
||||
values that match the pattern. This is how we can extract values out of enum
|
||||
variants.</p>
|
||||
<p>As an example, let’s change one of our enum variants to hold data inside it.
|
||||
From 1999 through 2008, the United States minted quarters with different
|
||||
designs for each of the 50 states on one side. No other coins got state
|
||||
designs, so only quarters have this extra value. We can add this information to
|
||||
our <code>enum</code> by changing the <code>Quarter</code> variant to include a <code>UsState</code> value
|
||||
stored inside it, which we’ve done in Listing 6-4.</p>
|
||||
<figure class="listing" id="listing-6-4">
|
||||
<pre class="playground"><code class="language-rust edition2024">#[derive(Debug)] // so we can inspect the state in a minute
|
||||
enum UsState {
|
||||
Alabama,
|
||||
Alaska,
|
||||
// --snip--
|
||||
}
|
||||
|
||||
enum Coin {
|
||||
Penny,
|
||||
Nickel,
|
||||
Dime,
|
||||
Quarter(UsState),
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-4">Listing 6-4</a>: A <code>Coin</code> enum in which the <code>Quarter</code> variant also holds a <code>UsState</code> value</figcaption>
|
||||
</figure>
|
||||
<p>Let’s imagine that a friend is trying to collect all 50 state quarters. While
|
||||
we sort our loose change by coin type, we’ll also call out the name of the
|
||||
state associated with each quarter so that if it’s one our friend doesn’t have,
|
||||
they can add it to their collection.</p>
|
||||
<p>In the match expression for this code, we add a variable called <code>state</code> to the
|
||||
pattern that matches values of the variant <code>Coin::Quarter</code>. When a
|
||||
<code>Coin::Quarter</code> matches, the <code>state</code> variable will bind to the value of that
|
||||
quarter’s state. Then, we can use <code>state</code> in the code for that arm, like so:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">enum UsState {
|
||||
</span><span class="boring"> Alabama,
|
||||
</span><span class="boring"> Alaska,
|
||||
</span><span class="boring"> // --snip--
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">enum Coin {
|
||||
</span><span class="boring"> Penny,
|
||||
</span><span class="boring"> Nickel,
|
||||
</span><span class="boring"> Dime,
|
||||
</span><span class="boring"> Quarter(UsState),
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn value_in_cents(coin: Coin) -> u8 {
|
||||
match coin {
|
||||
Coin::Penny => 1,
|
||||
Coin::Nickel => 5,
|
||||
Coin::Dime => 10,
|
||||
Coin::Quarter(state) => {
|
||||
println!("State quarter from {state:?}!");
|
||||
25
|
||||
}
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> value_in_cents(Coin::Quarter(UsState::Alaska));
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<p>If we were to call <code>value_in_cents(Coin::Quarter(UsState::Alaska))</code>, <code>coin</code>
|
||||
would be <code>Coin::Quarter(UsState::Alaska)</code>. When we compare that value with each
|
||||
of the match arms, none of them match until we reach <code>Coin::Quarter(state)</code>. At
|
||||
that point, the binding for <code>state</code> will be the value <code>UsState::Alaska</code>. We can
|
||||
then use that binding in the <code>println!</code> expression, thus getting the inner
|
||||
state value out of the <code>Coin</code> enum variant for <code>Quarter</code>.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="matching-with-optiont"></a></p>
|
||||
<h3 id="the-optiont-match-pattern"><a class="header" href="#the-optiont-match-pattern">The <code>Option<T></code> <code>match</code> Pattern</a></h3>
|
||||
<p>In the previous section, we wanted to get the inner <code>T</code> value out of the <code>Some</code>
|
||||
case when using <code>Option<T></code>; we can also handle <code>Option<T></code> using <code>match</code>, as
|
||||
we did with the <code>Coin</code> enum! Instead of comparing coins, we’ll compare the
|
||||
variants of <code>Option<T></code>, but the way the <code>match</code> expression works remains the
|
||||
same.</p>
|
||||
<p>Let’s say we want to write a function that takes an <code>Option<i32></code> and, if
|
||||
there’s a value inside, adds 1 to that value. If there isn’t a value inside,
|
||||
the function should return the <code>None</code> value and not attempt to perform any
|
||||
operations.</p>
|
||||
<p>This function is very easy to write, thanks to <code>match</code>, and will look like
|
||||
Listing 6-5.</p>
|
||||
<figure class="listing" id="listing-6-5">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
match x {
|
||||
None => None,
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
}
|
||||
|
||||
let five = Some(5);
|
||||
let six = plus_one(five);
|
||||
let none = plus_one(None);
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-5">Listing 6-5</a>: A function that uses a <code>match</code> expression on an <code>Option<i32></code></figcaption>
|
||||
</figure>
|
||||
<p>Let’s examine the first execution of <code>plus_one</code> in more detail. When we call
|
||||
<code>plus_one(five)</code>, the variable <code>x</code> in the body of <code>plus_one</code> will have the
|
||||
value <code>Some(5)</code>. We then compare that against each match arm:</p>
|
||||
<pre><code class="language-rust ignore"><span class="boring">fn main() {
|
||||
</span><span class="boring"> fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
</span><span class="boring"> match x {
|
||||
</span> None => None,
|
||||
<span class="boring"> Some(i) => Some(i + 1),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let five = Some(5);
|
||||
</span><span class="boring"> let six = plus_one(five);
|
||||
</span><span class="boring"> let none = plus_one(None);
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<p>The <code>Some(5)</code> value doesn’t match the pattern <code>None</code>, so we continue to the
|
||||
next arm:</p>
|
||||
<pre><code class="language-rust ignore"><span class="boring">fn main() {
|
||||
</span><span class="boring"> fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
</span><span class="boring"> match x {
|
||||
</span><span class="boring"> None => None,
|
||||
</span> Some(i) => Some(i + 1),
|
||||
<span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let five = Some(5);
|
||||
</span><span class="boring"> let six = plus_one(five);
|
||||
</span><span class="boring"> let none = plus_one(None);
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<p>Does <code>Some(5)</code> match <code>Some(i)</code>? It does! We have the same variant. The <code>i</code>
|
||||
binds to the value contained in <code>Some</code>, so <code>i</code> takes the value <code>5</code>. The code in
|
||||
the match arm is then executed, so we add 1 to the value of <code>i</code> and create a
|
||||
new <code>Some</code> value with our total <code>6</code> inside.</p>
|
||||
<p>Now let’s consider the second call of <code>plus_one</code> in Listing 6-5, where <code>x</code> is
|
||||
<code>None</code>. We enter the <code>match</code> and compare to the first arm:</p>
|
||||
<pre><code class="language-rust ignore"><span class="boring">fn main() {
|
||||
</span><span class="boring"> fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
</span><span class="boring"> match x {
|
||||
</span> None => None,
|
||||
<span class="boring"> Some(i) => Some(i + 1),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let five = Some(5);
|
||||
</span><span class="boring"> let six = plus_one(five);
|
||||
</span><span class="boring"> let none = plus_one(None);
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<p>It matches! There’s no value to add to, so the program stops and returns the
|
||||
<code>None</code> value on the right side of <code>=></code>. Because the first arm matched, no other
|
||||
arms are compared.</p>
|
||||
<p>Combining <code>match</code> and enums is useful in many situations. You’ll see this
|
||||
pattern a lot in Rust code: <code>match</code> against an enum, bind a variable to the
|
||||
data inside, and then execute code based on it. It’s a bit tricky at first, but
|
||||
once you get used to it, you’ll wish you had it in all languages. It’s
|
||||
consistently a user favorite.</p>
|
||||
<h3 id="matches-are-exhaustive"><a class="header" href="#matches-are-exhaustive">Matches Are Exhaustive</a></h3>
|
||||
<p>There’s one other aspect of <code>match</code> we need to discuss: The arms’ patterns must
|
||||
cover all possibilities. Consider this version of our <code>plus_one</code> function,
|
||||
which has a bug and won’t compile:</p>
|
||||
<pre><code class="language-rust ignore does_not_compile"><span class="boring">fn main() {
|
||||
</span> fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
match x {
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring"> let five = Some(5);
|
||||
</span><span class="boring"> let six = plus_one(five);
|
||||
</span><span class="boring"> let none = plus_one(None);
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<p>We didn’t handle the <code>None</code> case, so this code will cause a bug. Luckily, it’s
|
||||
a bug Rust knows how to catch. If we try to compile this code, we’ll get this
|
||||
error:</p>
|
||||
<pre><code class="language-console">$ cargo run
|
||||
Compiling enums v0.1.0 (file:///projects/enums)
|
||||
error[E0004]: non-exhaustive patterns: `None` not covered
|
||||
--> src/main.rs:3:15
|
||||
|
|
||||
3 | match x {
|
||||
| ^ pattern `None` not covered
|
||||
|
|
||||
note: `Option<i32>` defined here
|
||||
--> /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/core/src/option.rs:593:1
|
||||
::: /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/core/src/option.rs:597:5
|
||||
|
|
||||
= note: not covered
|
||||
= note: the matched value is of type `Option<i32>`
|
||||
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
||||
|
|
||||
4 ~ Some(i) => Some(i + 1),
|
||||
5 ~ None => todo!(),
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0004`.
|
||||
error: could not compile `enums` (bin "enums") due to 1 previous error
|
||||
</code></pre>
|
||||
<p>Rust knows that we didn’t cover every possible case and even knows which
|
||||
pattern we forgot! Matches in Rust are <em>exhaustive</em>: We must exhaust every last
|
||||
possibility in order for the code to be valid. Especially in the case of
|
||||
<code>Option<T></code>, when Rust prevents us from forgetting to explicitly handle the
|
||||
<code>None</code> case, it protects us from assuming that we have a value when we might
|
||||
have null, thus making the billion-dollar mistake discussed earlier impossible.</p>
|
||||
<h3 id="catch-all-patterns-and-the-_-placeholder"><a class="header" href="#catch-all-patterns-and-the-_-placeholder">Catch-All Patterns and the <code>_</code> Placeholder</a></h3>
|
||||
<p>Using enums, we can also take special actions for a few particular values, but
|
||||
for all other values take one default action. Imagine we’re implementing a game
|
||||
where, if you roll a 3 on a dice roll, your player doesn’t move but instead
|
||||
gets a fancy new hat. If you roll a 7, your player loses a fancy hat. For all
|
||||
other values, your player moves that number of spaces on the game board. Here’s
|
||||
a <code>match</code> that implements that logic, with the result of the dice roll
|
||||
hardcoded rather than a random value, and all other logic represented by
|
||||
functions without bodies because actually implementing them is out of scope for
|
||||
this example:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> let dice_roll = 9;
|
||||
match dice_roll {
|
||||
3 => add_fancy_hat(),
|
||||
7 => remove_fancy_hat(),
|
||||
other => move_player(other),
|
||||
}
|
||||
|
||||
fn add_fancy_hat() {}
|
||||
fn remove_fancy_hat() {}
|
||||
fn move_player(num_spaces: u8) {}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>For the first two arms, the patterns are the literal values <code>3</code> and <code>7</code>. For
|
||||
the last arm that covers every other possible value, the pattern is the
|
||||
variable we’ve chosen to name <code>other</code>. The code that runs for the <code>other</code> arm
|
||||
uses the variable by passing it to the <code>move_player</code> function.</p>
|
||||
<p>This code compiles, even though we haven’t listed all the possible values a
|
||||
<code>u8</code> can have, because the last pattern will match all values not specifically
|
||||
listed. This catch-all pattern meets the requirement that <code>match</code> must be
|
||||
exhaustive. Note that we have to put the catch-all arm last because the
|
||||
patterns are evaluated in order. If we had put the catch-all arm earlier, the
|
||||
other arms would never run, so Rust will warn us if we add arms after a
|
||||
catch-all!</p>
|
||||
<p>Rust also has a pattern we can use when we want a catch-all but don’t want to
|
||||
<em>use</em> the value in the catch-all pattern: <code>_</code> is a special pattern that matches
|
||||
any value and does not bind to that value. This tells Rust we aren’t going to
|
||||
use the value, so Rust won’t warn us about an unused variable.</p>
|
||||
<p>Let’s change the rules of the game: Now, if you roll anything other than a 3 or
|
||||
a 7, you must roll again. We no longer need to use the catch-all value, so we
|
||||
can change our code to use <code>_</code> instead of the variable named <code>other</code>:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> let dice_roll = 9;
|
||||
match dice_roll {
|
||||
3 => add_fancy_hat(),
|
||||
7 => remove_fancy_hat(),
|
||||
_ => reroll(),
|
||||
}
|
||||
|
||||
fn add_fancy_hat() {}
|
||||
fn remove_fancy_hat() {}
|
||||
fn reroll() {}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>This example also meets the exhaustiveness requirement because we’re explicitly
|
||||
ignoring all other values in the last arm; we haven’t forgotten anything.</p>
|
||||
<p>Finally, we’ll change the rules of the game one more time so that nothing else
|
||||
happens on your turn if you roll anything other than a 3 or a 7. We can express
|
||||
that by using the unit value (the empty tuple type we mentioned in <a href="../ch03/ch03-02-data-types.html#the-tuple-type">“The Tuple
|
||||
Type”</a><!-- ignore --> section) as the code that goes with the <code>_</code> arm:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> let dice_roll = 9;
|
||||
match dice_roll {
|
||||
3 => add_fancy_hat(),
|
||||
7 => remove_fancy_hat(),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
fn add_fancy_hat() {}
|
||||
fn remove_fancy_hat() {}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>Here, we’re telling Rust explicitly that we aren’t going to use any other value
|
||||
that doesn’t match a pattern in an earlier arm, and we don’t want to run any
|
||||
code in this case.</p>
|
||||
<p>There’s more about patterns and matching that we’ll cover in <a href="../ch19/ch19-00-patterns.html">Chapter
|
||||
19</a><!-- ignore -->. For now, we’re going to move on to the
|
||||
<code>if let</code> syntax, which can be useful in situations where the <code>match</code> expression
|
||||
is a bit wordy.</p>
|
||||
</body>
|
||||
</html>
|
||||
319
ch06/ch06-03-if-let.html
Normal file
319
ch06/ch06-03-if-let.html
Normal file
@@ -0,0 +1,319 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Concise Control Flow with if let and let...else</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="concise-control-flow-with-if-let-and-letelse"><a class="header" href="#concise-control-flow-with-if-let-and-letelse">Concise Control Flow with <code>if let</code> and <code>let...else</code></a></h2>
|
||||
<p>The <code>if let</code> syntax lets you combine <code>if</code> and <code>let</code> into a less verbose way to
|
||||
handle values that match one pattern while ignoring the rest. Consider the
|
||||
program in Listing 6-6 that matches on an <code>Option<u8></code> value in the
|
||||
<code>config_max</code> variable but only wants to execute code if the value is the <code>Some</code>
|
||||
variant.</p>
|
||||
<figure class="listing" id="listing-6-6">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> let config_max = Some(3u8);
|
||||
match config_max {
|
||||
Some(max) => println!("The maximum is configured to be {max}"),
|
||||
_ => (),
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-6">Listing 6-6</a>: A <code>match</code> that only cares about executing code when the value is <code>Some</code></figcaption>
|
||||
</figure>
|
||||
<p>If the value is <code>Some</code>, we print out the value in the <code>Some</code> variant by binding
|
||||
the value to the variable <code>max</code> in the pattern. We don’t want to do anything
|
||||
with the <code>None</code> value. To satisfy the <code>match</code> expression, we have to add <code>_ => ()</code> after processing just one variant, which is annoying boilerplate code to
|
||||
add.</p>
|
||||
<p>Instead, we could write this in a shorter way using <code>if let</code>. The following
|
||||
code behaves the same as the <code>match</code> in Listing 6-6:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> let config_max = Some(3u8);
|
||||
if let Some(max) = config_max {
|
||||
println!("The maximum is configured to be {max}");
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>The syntax <code>if let</code> takes a pattern and an expression separated by an equal
|
||||
sign. It works the same way as a <code>match</code>, where the expression is given to the
|
||||
<code>match</code> and the pattern is its first arm. In this case, the pattern is
|
||||
<code>Some(max)</code>, and the <code>max</code> binds to the value inside the <code>Some</code>. We can then
|
||||
use <code>max</code> in the body of the <code>if let</code> block in the same way we used <code>max</code> in
|
||||
the corresponding <code>match</code> arm. The code in the <code>if let</code> block only runs if the
|
||||
value matches the pattern.</p>
|
||||
<p>Using <code>if let</code> means less typing, less indentation, and less boilerplate code.
|
||||
However, you lose the exhaustive checking <code>match</code> enforces that ensures that
|
||||
you aren’t forgetting to handle any cases. Choosing between <code>match</code> and <code>if let</code> depends on what you’re doing in your particular situation and whether
|
||||
gaining conciseness is an appropriate trade-off for losing exhaustive checking.</p>
|
||||
<p>In other words, you can think of <code>if let</code> as syntax sugar for a <code>match</code> that
|
||||
runs code when the value matches one pattern and then ignores all other values.</p>
|
||||
<p>We can include an <code>else</code> with an <code>if let</code>. The block of code that goes with the
|
||||
<code>else</code> is the same as the block of code that would go with the <code>_</code> case in the
|
||||
<code>match</code> expression that is equivalent to the <code>if let</code> and <code>else</code>. Recall the
|
||||
<code>Coin</code> enum definition in Listing 6-4, where the <code>Quarter</code> variant also held a
|
||||
<code>UsState</code> value. If we wanted to count all non-quarter coins we see while also
|
||||
announcing the state of the quarters, we could do that with a <code>match</code>
|
||||
expression, like this:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">enum UsState {
|
||||
</span><span class="boring"> Alabama,
|
||||
</span><span class="boring"> Alaska,
|
||||
</span><span class="boring"> // --snip--
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">enum Coin {
|
||||
</span><span class="boring"> Penny,
|
||||
</span><span class="boring"> Nickel,
|
||||
</span><span class="boring"> Dime,
|
||||
</span><span class="boring"> Quarter(UsState),
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let coin = Coin::Penny;
|
||||
</span> let mut count = 0;
|
||||
match coin {
|
||||
Coin::Quarter(state) => println!("State quarter from {state:?}!"),
|
||||
_ => count += 1,
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>Or we could use an <code>if let</code> and <code>else</code> expression, like this:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">enum UsState {
|
||||
</span><span class="boring"> Alabama,
|
||||
</span><span class="boring"> Alaska,
|
||||
</span><span class="boring"> // --snip--
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">enum Coin {
|
||||
</span><span class="boring"> Penny,
|
||||
</span><span class="boring"> Nickel,
|
||||
</span><span class="boring"> Dime,
|
||||
</span><span class="boring"> Quarter(UsState),
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let coin = Coin::Penny;
|
||||
</span> let mut count = 0;
|
||||
if let Coin::Quarter(state) = coin {
|
||||
println!("State quarter from {state:?}!");
|
||||
} else {
|
||||
count += 1;
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<h2 id="staying-on-the-happy-path-with-letelse"><a class="header" href="#staying-on-the-happy-path-with-letelse">Staying on the “Happy Path” with <code>let...else</code></a></h2>
|
||||
<p>The common pattern is to perform some computation when a value is present and
|
||||
return a default value otherwise. Continuing with our example of coins with a
|
||||
<code>UsState</code> value, if we wanted to say something funny depending on how old the
|
||||
state on the quarter was, we might introduce a method on <code>UsState</code> to check the
|
||||
age of a state, like so:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#[derive(Debug)] // so we can inspect the state in a minute
|
||||
</span><span class="boring">enum UsState {
|
||||
</span><span class="boring"> Alabama,
|
||||
</span><span class="boring"> Alaska,
|
||||
</span><span class="boring"> // --snip--
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>impl UsState {
|
||||
fn existed_in(&self, year: u16) -> bool {
|
||||
match self {
|
||||
UsState::Alabama => year >= 1819,
|
||||
UsState::Alaska => year >= 1959,
|
||||
// -- snip --
|
||||
}
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">enum Coin {
|
||||
</span><span class="boring"> Penny,
|
||||
</span><span class="boring"> Nickel,
|
||||
</span><span class="boring"> Dime,
|
||||
</span><span class="boring"> Quarter(UsState),
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn describe_state_quarter(coin: Coin) -> Option<String> {
|
||||
</span><span class="boring"> if let Coin::Quarter(state) = coin {
|
||||
</span><span class="boring"> if state.existed_in(1900) {
|
||||
</span><span class="boring"> Some(format!("{state:?} is pretty old, for America!"))
|
||||
</span><span class="boring"> } else {
|
||||
</span><span class="boring"> Some(format!("{state:?} is relatively new."))
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> } else {
|
||||
</span><span class="boring"> None
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
|
||||
</span><span class="boring"> println!("{desc}");
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<p>Then, we might use <code>if let</code> to match on the type of coin, introducing a <code>state</code>
|
||||
variable within the body of the condition, as in Listing 6-7.</p>
|
||||
<figure class="listing" id="listing-6-7">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#[derive(Debug)] // so we can inspect the state in a minute
|
||||
</span><span class="boring">enum UsState {
|
||||
</span><span class="boring"> Alabama,
|
||||
</span><span class="boring"> Alaska,
|
||||
</span><span class="boring"> // --snip--
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl UsState {
|
||||
</span><span class="boring"> fn existed_in(&self, year: u16) -> bool {
|
||||
</span><span class="boring"> match self {
|
||||
</span><span class="boring"> UsState::Alabama => year >= 1819,
|
||||
</span><span class="boring"> UsState::Alaska => year >= 1959,
|
||||
</span><span class="boring"> // -- snip --
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">enum Coin {
|
||||
</span><span class="boring"> Penny,
|
||||
</span><span class="boring"> Nickel,
|
||||
</span><span class="boring"> Dime,
|
||||
</span><span class="boring"> Quarter(UsState),
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn describe_state_quarter(coin: Coin) -> Option<String> {
|
||||
if let Coin::Quarter(state) = coin {
|
||||
if state.existed_in(1900) {
|
||||
Some(format!("{state:?} is pretty old, for America!"))
|
||||
} else {
|
||||
Some(format!("{state:?} is relatively new."))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
|
||||
</span><span class="boring"> println!("{desc}");
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-7">Listing 6-7</a>: Checking whether a state existed in 1900 by using conditionals nested inside an <code>if let</code></figcaption>
|
||||
</figure>
|
||||
<p>That gets the job done, but it has pushed the work into the body of the <code>if let</code> statement, and if the work to be done is more complicated, it might be
|
||||
hard to follow exactly how the top-level branches relate. We could also take
|
||||
advantage of the fact that expressions produce a value either to produce the
|
||||
<code>state</code> from the <code>if let</code> or to return early, as in Listing 6-8. (You could do
|
||||
something similar with a <code>match</code>, too.)</p>
|
||||
<figure class="listing" id="listing-6-8">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#[derive(Debug)] // so we can inspect the state in a minute
|
||||
</span><span class="boring">enum UsState {
|
||||
</span><span class="boring"> Alabama,
|
||||
</span><span class="boring"> Alaska,
|
||||
</span><span class="boring"> // --snip--
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl UsState {
|
||||
</span><span class="boring"> fn existed_in(&self, year: u16) -> bool {
|
||||
</span><span class="boring"> match self {
|
||||
</span><span class="boring"> UsState::Alabama => year >= 1819,
|
||||
</span><span class="boring"> UsState::Alaska => year >= 1959,
|
||||
</span><span class="boring"> // -- snip --
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">enum Coin {
|
||||
</span><span class="boring"> Penny,
|
||||
</span><span class="boring"> Nickel,
|
||||
</span><span class="boring"> Dime,
|
||||
</span><span class="boring"> Quarter(UsState),
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn describe_state_quarter(coin: Coin) -> Option<String> {
|
||||
let state = if let Coin::Quarter(state) = coin {
|
||||
state
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if state.existed_in(1900) {
|
||||
Some(format!("{state:?} is pretty old, for America!"))
|
||||
} else {
|
||||
Some(format!("{state:?} is relatively new."))
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
|
||||
</span><span class="boring"> println!("{desc}");
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-8">Listing 6-8</a>: Using <code>if let</code> to produce a value or return early</figcaption>
|
||||
</figure>
|
||||
<p>This is a bit annoying to follow in its own way, though! One branch of the <code>if let</code> produces a value, and the other one returns from the function entirely.</p>
|
||||
<p>To make this common pattern nicer to express, Rust has <code>let...else</code>. The
|
||||
<code>let...else</code> syntax takes a pattern on the left side and an expression on the
|
||||
right, very similar to <code>if let</code>, but it does not have an <code>if</code> branch, only an
|
||||
<code>else</code> branch. If the pattern matches, it will bind the value from the pattern
|
||||
in the outer scope. If the pattern does <em>not</em> match, the program will flow into
|
||||
the <code>else</code> arm, which must return from the function.</p>
|
||||
<p>In Listing 6-9, you can see how Listing 6-8 looks when using <code>let...else</code> in
|
||||
place of <code>if let</code>.</p>
|
||||
<figure class="listing" id="listing-6-9">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#[derive(Debug)] // so we can inspect the state in a minute
|
||||
</span><span class="boring">enum UsState {
|
||||
</span><span class="boring"> Alabama,
|
||||
</span><span class="boring"> Alaska,
|
||||
</span><span class="boring"> // --snip--
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl UsState {
|
||||
</span><span class="boring"> fn existed_in(&self, year: u16) -> bool {
|
||||
</span><span class="boring"> match self {
|
||||
</span><span class="boring"> UsState::Alabama => year >= 1819,
|
||||
</span><span class="boring"> UsState::Alaska => year >= 1959,
|
||||
</span><span class="boring"> // -- snip --
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">enum Coin {
|
||||
</span><span class="boring"> Penny,
|
||||
</span><span class="boring"> Nickel,
|
||||
</span><span class="boring"> Dime,
|
||||
</span><span class="boring"> Quarter(UsState),
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn describe_state_quarter(coin: Coin) -> Option<String> {
|
||||
let Coin::Quarter(state) = coin else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if state.existed_in(1900) {
|
||||
Some(format!("{state:?} is pretty old, for America!"))
|
||||
} else {
|
||||
Some(format!("{state:?} is relatively new."))
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
|
||||
</span><span class="boring"> println!("{desc}");
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-6-9">Listing 6-9</a>: Using <code>let...else</code> to clarify the flow through the function</figcaption>
|
||||
</figure>
|
||||
<p>Notice that it stays on the “happy path” in the main body of the function this
|
||||
way, without having significantly different control flow for two branches the
|
||||
way the <code>if let</code> did.</p>
|
||||
<p>If you have a situation in which your program has logic that is too verbose to
|
||||
express using a <code>match</code>, remember that <code>if let</code> and <code>let...else</code> are in your
|
||||
Rust toolbox as well.</p>
|
||||
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
|
||||
<p>We’ve now covered how to use enums to create custom types that can be one of a
|
||||
set of enumerated values. We’ve shown how the standard library’s <code>Option<T></code>
|
||||
type helps you use the type system to prevent errors. When enum values have
|
||||
data inside them, you can use <code>match</code> or <code>if let</code> to extract and use those
|
||||
values, depending on how many cases you need to handle.</p>
|
||||
<p>Your Rust programs can now express concepts in your domain using structs and
|
||||
enums. Creating custom types to use in your API ensures type safety: The
|
||||
compiler will make certain your functions only get values of the type each
|
||||
function expects.</p>
|
||||
<p>In order to provide a well-organized API to your users that is straightforward
|
||||
to use and only exposes exactly what your users will need, let’s now turn to
|
||||
Rust’s modules.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user