Skip to content

Commit

Permalink
Ch. 6: Introduce let-else after if let.
Browse files Browse the repository at this point in the history
This is not technically part of the 2024 Edition, since it is available
on the 2021 edition as well, but it *will* be an important update for
the 2024 Edition revision of the print book!
  • Loading branch information
chriskrycho committed Dec 13, 2024
1 parent e78a93c commit d8792a4
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 3 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "enums"
version = "0.1.0"
edition = "2021"

[dependencies]
45 changes: 45 additions & 0 deletions listings/ch06-enums-and-pattern-matching/listing-06-07/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}

// ANCHOR: state
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
// ANCHOR_END: state

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

// ANCHOR: describe
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
}
}
// ANCHOR_END: describe

fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "enums"
version = "0.1.0"
edition = "2021"

[dependencies]
45 changes: 45 additions & 0 deletions listings/ch06-enums-and-pattern-matching/listing-06-08/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}

impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

// ANCHOR: describe
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."))
}
}
// ANCHOR_END: describe

fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "enums"
version = "0.1.0"
edition = "2021"

[dependencies]
43 changes: 43 additions & 0 deletions listings/ch06-enums-and-pattern-matching/listing-06-09/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}

impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

// ANCHOR: describe
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."))
}
}
// ANCHOR_END: describe

fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
2 changes: 1 addition & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
- [Enums and Pattern Matching](ch06-00-enums.md)
- [Defining an Enum](ch06-01-defining-an-enum.md)
- [The `match` Control Flow Construct](ch06-02-match.md)
- [Concise Control Flow with `if let`](ch06-03-if-let.md)
- [Concise Control Flow with `if let` and `let else`](ch06-03-if-let.md)

## Basic Rust Literacy

Expand Down
66 changes: 64 additions & 2 deletions src/ch06-03-if-let.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Concise Control Flow with `if let`
## Concise Control Flow with `if let` and `let else`

The `if let` syntax lets you combine `if` and `let` into a less verbose way to
handle values that match one pattern while ignoring the rest. Consider the
Expand Down Expand Up @@ -62,8 +62,70 @@ Or we could use an `if let` and `else` expression, like this:
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-14-count-and-announce-if-let-else/src/main.rs:here}}
```

## Staying on the “happy path” with `let else`

One common pattern is to perform some computation when a value is present and
return a default value otherwise. Continuing on with our example of coins with a
`UsState` value, if we wanted to say something funny depending on how old the
state on the quarter was, we might introduce a method on `UsState` to check the
age of a state, like so:

```rust
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-07/src/main.rs:state}}
```

Then we might use `if let` to match on the type of coin, introducing a `state`
variable within the body of the condition, as in Listing 6-7.

<Listing number="6-7" caption="Using" file-name="src/main.rs">

```rust
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-07/src/main.rs:describe}}
```

</Listing>

That gets the job done, but it has pushed the work into the body of the `if let`
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 `state` from
the `if let` or to return early, as in Listing 6-8. (You could do similar with a
`match`, of course!)

<Listing number="6-8" caption="Using `if let` to produce a value or return early." file-name="src/main.rs">

```rust
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-08/src/main.rs:describe}}
```

</Listing>

This is a bit annoying to follow in its own way, though! One branch of the `if
let` produces a value, and the other one returns from the function entirely.

To make this common pattern nicer to express, Rust has `let`-`else`. The
`let`-`else` syntax takes a pattern on the left side and an expression on the
right, very similar to `if let`, but it does not have an `if` branch, only an
`else` branch. If the pattern matches, it will bind the value from the pattern
in the outer scope. If the pattern does *not* match, the program will flow into
the `else` arm, which must return from the function.

In Listing 6-9, you can see how Listing 6-8 looks when using `let`-`else` in
place of `if let`. 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 `if let` did.

<Listing number="6-9" caption="Using `let`-`else` to clarify the flow through the function." file-name="src/main.rs">

```rust
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-09/src/main.rs:describe}}
```

</Listing>

If you have a situation in which your program has logic that is too verbose to
express using a `match`, remember that `if let` is in your Rust toolbox as well.
express using a `match`, remember that `if let` and `let else` are in your Rust
toolbox as well.

## Summary

Expand Down

0 comments on commit d8792a4

Please sign in to comment.