mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-03-21 14:46:37 +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 Structs](pattern-matching/destructuring-structs.md)
|
||||||
- [Destructuring Enums](pattern-matching/destructuring-enums.md)
|
- [Destructuring Enums](pattern-matching/destructuring-enums.md)
|
||||||
- [Let Control Flow](pattern-matching/let-control-flow.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)
|
- [Exercise: Expression Evaluation](pattern-matching/exercise.md)
|
||||||
- [Solution](pattern-matching/solution.md)
|
- [Solution](pattern-matching/solution.md)
|
||||||
- [Methods and Traits](methods-and-traits.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:
|
are used for pattern matching:
|
||||||
|
|
||||||
- `if let` expressions
|
- `if let` expressions
|
||||||
- `let else` expressions
|
|
||||||
- `while let` expressions
|
- `while let` expressions
|
||||||
|
- `let else` 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>
|
|
||||||
|
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