feat: added cleanscript
This commit is contained in:
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