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:
parent
f19bb8f10d
commit
3b7442a498
@ -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)
|
||||
|
@ -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
|
||||
|
31
src/pattern-matching/let-control-flow/if-let.md
Normal file
31
src/pattern-matching/let-control-flow/if-let.md
Normal 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>
|
57
src/pattern-matching/let-control-flow/let-else.md
Normal file
57
src/pattern-matching/let-control-flow/let-else.md
Normal 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>
|
34
src/pattern-matching/let-control-flow/while-let.md
Normal file
34
src/pattern-matching/let-control-flow/while-let.md
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user