Skip to content

Commit

Permalink
Merge pull request #3702 from veera-sivarajan/add-letElse
Browse files Browse the repository at this point in the history
Introduce `let`-`else` statement
  • Loading branch information
chriskrycho authored Dec 13, 2024
2 parents e78a93c + 1787dd1 commit cd3b1c0
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 8 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
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

0 comments on commit cd3b1c0

Please sign in to comment.