Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce let-else statement #3702

Merged
merged 3 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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
10 changes: 5 additions & 5 deletions src/ch19-02-refutability.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ a_value` because if the value in the `a_value` variable is `None` rather than

Function parameters, `let` statements, and `for` loops can only accept
irrefutable patterns, because the program cannot do anything meaningful when
values don’t match. The `if let` and `while let` expressions accept
refutable and irrefutable patterns, but the compiler warns against
irrefutable patterns because by definition they’re intended to handle possible
failure: the functionality of a conditional is in its ability to perform
differently depending on success or failure.
values don’t match. The `if let` and `while let` expressions and the
`let`-`else` statement accept refutable and irrefutable patterns, but the
compiler warns against irrefutable patterns because by definition they’re
intended to handle possible failure: the functionality of a conditional is in
its ability to perform differently depending on success or failure.

In general, you shouldn’t have to worry about the distinction between refutable
and irrefutable patterns; however, you do need to be familiar with the concept
Expand Down
Loading