Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split let control flow into mutliple sub-slides #2567

Merged
merged 2 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
120 changes: 1 addition & 119 deletions src/pattern-matching/let-control-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 31 additions & 0 deletions src/pattern-matching/let-control-flow/if-let.md
Original file line number Diff line number Diff line change
@@ -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 changes: 57 additions & 0 deletions src/pattern-matching/let-control-flow/let-else.md
Original file line number Diff line number Diff line change
@@ -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 changes: 34 additions & 0 deletions src/pattern-matching/let-control-flow/while-let.md
Original file line number Diff line number Diff line change
@@ -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