Merge pull request #3702 from veera-sivarajan/add-letElse

Introduce `let`-`else` statement
This commit is contained in:
Chris Krycho
2024-12-13 14:58:51 -07:00
committed by GitHub
12 changed files with 239 additions and 8 deletions

View File

@@ -0,0 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "enums"
version = "0.1.0"

View File

@@ -0,0 +1,6 @@
[package]
name = "enums"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@@ -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}");
}
}

View File

@@ -0,0 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "enums"
version = "0.1.0"

View File

@@ -0,0 +1,6 @@
[package]
name = "enums"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@@ -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}");
}
}

View File

@@ -0,0 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "enums"
version = "0.1.0"

View File

@@ -0,0 +1,6 @@
[package]
name = "enums"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@@ -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}");
}
}

View File

@@ -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

View File

@@ -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
@@ -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

View File

@@ -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 dont match. The `if let` and `while let` expressions accept
refutable and irrefutable patterns, but the compiler warns against
irrefutable patterns because by definition theyre intended to handle possible
failure: the functionality of a conditional is in its ability to perform
differently depending on success or failure.
values dont 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 theyre
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 shouldnt have to worry about the distinction between refutable
and irrefutable patterns; however, you do need to be familiar with the concept