1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-20 22:36:03 +02:00

Split let control flow into mutliple sub-slides (#2567)

There are three kinds of syntax here, making for a very long and
hard-to-navigate slide. Splitting it up helps!
This commit is contained in:
Dustin J. Mitchell 2025-01-22 14:06:53 -05:00 committed by GitHub
parent f19bb8f10d
commit 3b7442a498
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 126 additions and 119 deletions

View File

@ -81,6 +81,9 @@
- [Destructuring Structs](pattern-matching/destructuring-structs.md)
- [Destructuring Enums](pattern-matching/destructuring-enums.md)
- [Let Control Flow](pattern-matching/let-control-flow.md)
- [`if let` Expressions](pattern-matching/let-control-flow/if-let.md)
- [`while let` Statements](pattern-matching/let-control-flow/while-let.md)
- [`let else`](pattern-matching/let-control-flow/let-else.md)
- [Exercise: Expression Evaluation](pattern-matching/exercise.md)
- [Solution](pattern-matching/solution.md)
- [Methods and Traits](methods-and-traits.md)

View File

@ -8,123 +8,5 @@ Rust has a few control flow constructs which differ from other languages. They
are used for pattern matching:
- `if let` expressions
- `let else` expressions
- `while let` expressions
# `if let` expressions
The
[`if let` expression](https://doc.rust-lang.org/reference/expressions/if-expr.html#if-let-expressions)
lets you execute different code depending on whether a value matches a pattern:
```rust,editable
use std::time::Duration;
fn sleep_for(secs: f32) {
if let Ok(duration) = Duration::try_from_secs_f32(secs) {
std::thread::sleep(duration);
println!("slept for {duration:?}");
}
}
fn main() {
sleep_for(-10.0);
sleep_for(0.8);
}
```
# `let else` expressions
For the common case of matching a pattern and returning from the function, use
[`let else`](https://doc.rust-lang.org/rust-by-example/flow_control/let_else.html).
The "else" case must diverge (`return`, `break`, or panic - anything but falling
off the end of the block).
```rust,editable
fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
// TODO: The structure of this code is difficult to follow -- rewrite it with let-else!
if let Some(s) = maybe_string {
if let Some(first_byte_char) = s.chars().next() {
if let Some(digit) = first_byte_char.to_digit(16) {
Ok(digit)
} else {
return Err(String::from("not a hex digit"));
}
} else {
return Err(String::from("got empty string"));
}
} else {
return Err(String::from("got None"));
}
}
fn main() {
println!("result: {:?}", hex_or_die_trying(Some(String::from("foo"))));
}
```
Like with `if let`, there is a
[`while let`](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-pattern-loops)
variant which repeatedly tests a value against a pattern:
<!-- mdbook-xgettext: skip -->
```rust,editable
fn main() {
let mut name = String::from("Comprehensive Rust 🦀");
while let Some(c) = name.pop() {
println!("character: {c}");
}
// (There are more efficient ways to reverse a string!)
}
```
Here
[`String::pop`](https://doc.rust-lang.org/stable/std/string/struct.String.html#method.pop)
returns `Some(c)` until the string is empty, after which it will return `None`.
The `while let` lets us keep iterating through all items.
<details>
## if-let
- Unlike `match`, `if let` does not have to cover all branches. This can make it
more concise than `match`.
- A common usage is handling `Some` values when working with `Option`.
- Unlike `match`, `if let` does not support guard clauses for pattern matching.
## let-else
`if-let`s can pile up, as shown. The `let-else` construct supports flattening
this nested code. Rewrite the awkward version for students, so they can see the
transformation.
The rewritten version is:
```rust
fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
let Some(s) = maybe_string else {
return Err(String::from("got None"));
};
let Some(first_byte_char) = s.chars().next() else {
return Err(String::from("got empty string"));
};
let Some(digit) = first_byte_char.to_digit(16) else {
return Err(String::from("not a hex digit"));
};
return Ok(digit);
}
```
# while-let
- Point out that the `while let` loop will keep going as long as the value
matches the pattern.
- You could rewrite the `while let` loop as an infinite loop with an if
statement that breaks when there is no value to unwrap for `name.pop()`. The
`while let` provides syntactic sugar for the above scenario.
</details>
- `let else` expressions

View File

@ -0,0 +1,31 @@
# `if let` Expressions
The
[`if let` expression](https://doc.rust-lang.org/reference/expressions/if-expr.html#if-let-expressions)
lets you execute different code depending on whether a value matches a pattern:
```rust,editable
use std::time::Duration;
fn sleep_for(secs: f32) {
if let Ok(duration) = Duration::try_from_secs_f32(secs) {
std::thread::sleep(duration);
println!("slept for {duration:?}");
}
}
fn main() {
sleep_for(-10.0);
sleep_for(0.8);
}
```
<details>
- Unlike `match`, `if let` does not have to cover all branches. This can make it
more concise than `match`.
- A common usage is handling `Some` values when working with `Option`.
- Unlike `match`, `if let` does not support guard clauses for pattern matching.
- With an `else` clause, this can be used as an expression.
</details>

View File

@ -0,0 +1,57 @@
# `let else` Statements
For the common case of matching a pattern and returning from the function, use
[`let else`](https://doc.rust-lang.org/rust-by-example/flow_control/let_else.html).
The "else" case must diverge (`return`, `break`, or panic - anything but falling
off the end of the block).
```rust,editable
fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
// TODO: The structure of this code is difficult to follow -- rewrite it with let-else!
if let Some(s) = maybe_string {
if let Some(first_byte_char) = s.chars().next() {
if let Some(digit) = first_byte_char.to_digit(16) {
Ok(digit)
} else {
Err(String::from("not a hex digit"))
}
} else {
Err(String::from("got empty string"))
}
} else {
Err(String::from("got None"))
}
}
fn main() {
println!("result: {:?}", hex_or_die_trying(Some(String::from("foo"))));
}
```
<details>
`if-let`s can pile up, as shown. The `let-else` construct supports flattening
this nested code. Rewrite the awkward version for students, so they can see the
transformation.
The rewritten version is:
```rust
fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
let Some(s) = maybe_string else {
return Err(String::from("got None"));
};
let Some(first_byte_char) = s.chars().next() else {
return Err(String::from("got empty string"));
};
let Some(digit) = first_byte_char.to_digit(16) else {
return Err(String::from("not a hex digit"));
};
Ok(digit)
}
```
</details>

View File

@ -0,0 +1,34 @@
# `while let` Statements
Like with `if let`, there is a
[`while let`](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-pattern-loops)
variant which repeatedly tests a value against a pattern:
<!-- mdbook-xgettext: skip -->
```rust,editable
fn main() {
let mut name = String::from("Comprehensive Rust 🦀");
while let Some(c) = name.pop() {
println!("character: {c}");
}
// (There are more efficient ways to reverse a string!)
}
```
Here
[`String::pop`](https://doc.rust-lang.org/stable/std/string/struct.String.html#method.pop)
returns `Some(c)` until the string is empty, after which it will return `None`.
The `while let` lets us keep iterating through all items.
<details>
- Point out that the `while let` loop will keep going as long as the value
matches the pattern.
- You could rewrite the `while let` loop as an infinite loop with an if
statement that breaks when there is no value to unwrap for `name.pop()`. The
`while let` provides syntactic sugar for the above scenario.
- This form cannot be used as an expression, because it may have no value if the
condition is false.
</details>