diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-07/Cargo.lock b/listings/ch06-enums-and-pattern-matching/listing-06-07/Cargo.lock new file mode 100644 index 0000000000..f62e8ac453 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-07/Cargo.lock @@ -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" + diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-07/Cargo.toml b/listings/ch06-enums-and-pattern-matching/listing-06-07/Cargo.toml new file mode 100644 index 0000000000..e959295f91 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-07/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "enums" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-07/src/main.rs b/listings/ch06-enums-and-pattern-matching/listing-06-07/src/main.rs new file mode 100644 index 0000000000..af7ec51179 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-07/src/main.rs @@ -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 { + 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}"); + } +} diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-08/Cargo.lock b/listings/ch06-enums-and-pattern-matching/listing-06-08/Cargo.lock new file mode 100644 index 0000000000..f62e8ac453 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-08/Cargo.lock @@ -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" + diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-08/Cargo.toml b/listings/ch06-enums-and-pattern-matching/listing-06-08/Cargo.toml new file mode 100644 index 0000000000..e959295f91 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-08/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "enums" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-08/src/main.rs b/listings/ch06-enums-and-pattern-matching/listing-06-08/src/main.rs new file mode 100644 index 0000000000..cde9f043d6 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-08/src/main.rs @@ -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 { + 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}"); + } +} diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-09/Cargo.lock b/listings/ch06-enums-and-pattern-matching/listing-06-09/Cargo.lock new file mode 100644 index 0000000000..f62e8ac453 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-09/Cargo.lock @@ -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" + diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-09/Cargo.toml b/listings/ch06-enums-and-pattern-matching/listing-06-09/Cargo.toml new file mode 100644 index 0000000000..e959295f91 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-09/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "enums" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/listings/ch06-enums-and-pattern-matching/listing-06-09/src/main.rs b/listings/ch06-enums-and-pattern-matching/listing-06-09/src/main.rs new file mode 100644 index 0000000000..cffa151901 --- /dev/null +++ b/listings/ch06-enums-and-pattern-matching/listing-06-09/src/main.rs @@ -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 { + 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}"); + } +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index a157cd05af..12a4c43ce5 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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 diff --git a/src/ch06-03-if-let.md b/src/ch06-03-if-let.md index 50ad9321f9..de9602ea8e 100644 --- a/src/ch06-03-if-let.md +++ b/src/ch06-03-if-let.md @@ -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. + ++ +```rust +{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-07/src/main.rs:describe}} +``` + + + +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!) + ++ +```rust +{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-08/src/main.rs:describe}} +``` + + + +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. + ++ +```rust +{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-09/src/main.rs:describe}} +``` + + + 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