mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-03-23 23:19:20 +02:00
Break closures into its own segment (#2574)
In teaching the course last week, we broke here, partly due to time
constraints, but partly because this is a pretty mind-bending topic to
tackle at the end of an information-dense day. A break helps, and
spreading the content over a few slides helps as well.
By the timings in the course, this leaves day 2 looking like
*Fundamentals // Day 2 Morning*
_1 hour and 55 minutes: (1 hour and 10 minutes short)_
* Welcome - _3 minutes_
* Pattern Matching - _45 minutes_
* Methods and Traits - _50 minutes_
*Fundamentals // Day 2 Afternoon*
_3 hours and 30 minutes (⏰ *30 minutes too long*)_
* Welcome - _0 minutes_
* Generics - _45 minutes_
* Standard Library Types - _1 hour_
* Standard Library Traits - _1 hour_
* Closures - _20 minutes_
Maybe we should move generics to the morning session?
This commit is contained in:
parent
3b7442a498
commit
9f9f845acc
@ -95,10 +95,6 @@
|
|||||||
- [Deriving](methods-and-traits/deriving.md)
|
- [Deriving](methods-and-traits/deriving.md)
|
||||||
- [Exercise: Generic Logger](methods-and-traits/exercise.md)
|
- [Exercise: Generic Logger](methods-and-traits/exercise.md)
|
||||||
- [Solution](methods-and-traits/solution.md)
|
- [Solution](methods-and-traits/solution.md)
|
||||||
|
|
||||||
# Day 2: Afternoon
|
|
||||||
|
|
||||||
- [Welcome](welcome-day-2-afternoon.md)
|
|
||||||
- [Generics](generics.md)
|
- [Generics](generics.md)
|
||||||
- [Generic Functions](generics/generic-functions.md)
|
- [Generic Functions](generics/generic-functions.md)
|
||||||
- [Generic Data Types](generics/generic-data.md)
|
- [Generic Data Types](generics/generic-data.md)
|
||||||
@ -108,6 +104,10 @@
|
|||||||
- [`dyn Trait`](generics/dyn-trait.md)
|
- [`dyn Trait`](generics/dyn-trait.md)
|
||||||
- [Exercise: Generic `min`](generics/exercise.md)
|
- [Exercise: Generic `min`](generics/exercise.md)
|
||||||
- [Solution](generics/solution.md)
|
- [Solution](generics/solution.md)
|
||||||
|
|
||||||
|
# Day 2: Afternoon
|
||||||
|
|
||||||
|
- [Welcome](welcome-day-2-afternoon.md)
|
||||||
- [Standard Library Types](std-types.md)
|
- [Standard Library Types](std-types.md)
|
||||||
- [Standard Library](std-types/std.md)
|
- [Standard Library](std-types/std.md)
|
||||||
- [Documentation](std-types/docs.md)
|
- [Documentation](std-types/docs.md)
|
||||||
@ -125,9 +125,14 @@
|
|||||||
- [Casting](std-traits/casting.md)
|
- [Casting](std-traits/casting.md)
|
||||||
- [`Read` and `Write`](std-traits/read-and-write.md)
|
- [`Read` and `Write`](std-traits/read-and-write.md)
|
||||||
- [`Default`, struct update syntax](std-traits/default.md)
|
- [`Default`, struct update syntax](std-traits/default.md)
|
||||||
- [Closures](std-traits/closures.md)
|
|
||||||
- [Exercise: ROT13](std-traits/exercise.md)
|
- [Exercise: ROT13](std-traits/exercise.md)
|
||||||
- [Solution](std-traits/solution.md)
|
- [Solution](std-traits/solution.md)
|
||||||
|
- [Closures](closures.md)
|
||||||
|
- [Closure Syntax](closures/syntax.md)
|
||||||
|
- [Capturing](closures/capturing.md)
|
||||||
|
- [Closure Traits](closures/traits.md)
|
||||||
|
- [Exercise: Log Filter](closures/exercise.md)
|
||||||
|
- [Solution](closures/solution.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
3
src/closures.md
Normal file
3
src/closures.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Closures
|
||||||
|
|
||||||
|
{{%segment outline}}
|
48
src/closures/capturing.md
Normal file
48
src/closures/capturing.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
minutes: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
# Capturing
|
||||||
|
|
||||||
|
A closure can capture variables from the environment where it was defined.
|
||||||
|
|
||||||
|
```rust,editable
|
||||||
|
fn main() {
|
||||||
|
let max_value = 5;
|
||||||
|
let clamp = |v| {
|
||||||
|
if v > max_value {
|
||||||
|
max_value
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"clamped values at {max_value}: {:?}",
|
||||||
|
(0..10).map(clamp).collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
- By default, a closure captures values by reference. Here `max_value` is
|
||||||
|
captured by `clamp`, but still available to `main` for printing. Try making
|
||||||
|
`max_value` mutable, changing it, and printing the clamped values again. Why
|
||||||
|
doesn't this work?
|
||||||
|
|
||||||
|
- If a closure mutates values, it will capture them by mutable reference. Try
|
||||||
|
adding `max_value += 1` to `clamp`.
|
||||||
|
|
||||||
|
- You can force a closure to move values instead of referencing them with the
|
||||||
|
`move` keyword. This can help with lifetimes, for example if the closure must
|
||||||
|
outlive the captured values (more on lifetimes later).
|
||||||
|
|
||||||
|
This looks like `move |v| ..`. Try adding this keyword and see if `main` can
|
||||||
|
still access `max_value` after defining `clamp`.
|
||||||
|
|
||||||
|
- By default, closures will capture each variable from an outer scope by the
|
||||||
|
least demanding form of access they can (by shared reference if possible, then
|
||||||
|
exclusive reference, then by move). The `move` keyword forces capture by
|
||||||
|
value.
|
||||||
|
|
||||||
|
</details>
|
13
src/closures/exercise.md
Normal file
13
src/closures/exercise.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Exercise: Log Filter
|
||||||
|
|
||||||
|
Building on the generic logger from this morning, implement a `Filter` which
|
||||||
|
uses a closure to filter log messages, sending those which pass the filtering
|
||||||
|
predicate to an inner logger.
|
||||||
|
|
||||||
|
```rust,compile_fail
|
||||||
|
{{#include exercise.rs:setup}}
|
||||||
|
|
||||||
|
// TODO: Define and implement `Filter`.
|
||||||
|
|
||||||
|
{{#include exercise.rs:main}}
|
||||||
|
```
|
69
src/closures/exercise.rs
Normal file
69
src/closures/exercise.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2024 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// ANCHOR: solution
|
||||||
|
// ANCHOR: setup
|
||||||
|
pub trait Logger {
|
||||||
|
/// Log a message at the given verbosity level.
|
||||||
|
fn log(&self, verbosity: u8, message: &str);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StderrLogger;
|
||||||
|
|
||||||
|
impl Logger for StderrLogger {
|
||||||
|
fn log(&self, verbosity: u8, message: &str) {
|
||||||
|
eprintln!("verbosity={verbosity}: {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: setup
|
||||||
|
|
||||||
|
/// Only log messages matching a filtering predicate.
|
||||||
|
struct Filter<L, P>
|
||||||
|
where
|
||||||
|
L: Logger,
|
||||||
|
P: Fn(u8, &str) -> bool,
|
||||||
|
{
|
||||||
|
inner: L,
|
||||||
|
predicate: P,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, P> Filter<L, P>
|
||||||
|
where
|
||||||
|
L: Logger,
|
||||||
|
P: Fn(u8, &str) -> bool,
|
||||||
|
{
|
||||||
|
fn new(inner: L, predicate: P) -> Self {
|
||||||
|
Self { inner, predicate }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<L, P> Logger for Filter<L, P>
|
||||||
|
where
|
||||||
|
L: Logger,
|
||||||
|
P: Fn(u8, &str) -> bool,
|
||||||
|
{
|
||||||
|
fn log(&self, verbosity: u8, message: &str) {
|
||||||
|
if (self.predicate)(verbosity, message) {
|
||||||
|
self.inner.log(verbosity, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: main
|
||||||
|
fn main() {
|
||||||
|
let logger = Filter::new(StderrLogger, |_verbosity, msg| msg.contains("yikes"));
|
||||||
|
logger.log(5, "FYI");
|
||||||
|
logger.log(1, "yikes, something went wrong");
|
||||||
|
logger.log(2, "uhoh");
|
||||||
|
}
|
||||||
|
// ANCHOR_END: main
|
5
src/closures/solution.md
Normal file
5
src/closures/solution.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Solution
|
||||||
|
|
||||||
|
```rust,editable
|
||||||
|
{{#include exercise.rs:solution}}
|
||||||
|
```
|
32
src/closures/syntax.md
Normal file
32
src/closures/syntax.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
minutes: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Closure Syntax
|
||||||
|
|
||||||
|
Closures are created with vertical bars: `|..| ..`.
|
||||||
|
|
||||||
|
```rust,editable
|
||||||
|
fn main() {
|
||||||
|
let value = Some(13);
|
||||||
|
dbg!(value.map(|num| format!("{num}")));
|
||||||
|
|
||||||
|
let mut nums = vec![1, 10, 99, 24];
|
||||||
|
// Sort even numbers first.
|
||||||
|
nums.sort_by_key(|v| if v % 2 == 0 { (0, *v) } else { (1, *v) });
|
||||||
|
dbg!(nums);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
- The arguments go between the `|..|`. The body can be surrounded by `{ .. }`,
|
||||||
|
but if it is a single expression these can be omitted.
|
||||||
|
|
||||||
|
- Argument types are optional, and are inferred if not given. The return type is
|
||||||
|
also optional, but can only be written if using `{ .. }` around the body.
|
||||||
|
|
||||||
|
- The examples are both lambdas -- they do not capture anything from their
|
||||||
|
environment. We will see captures next.
|
||||||
|
|
||||||
|
</details>
|
72
src/closures/traits.md
Normal file
72
src/closures/traits.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
minutes: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
# Closures
|
||||||
|
|
||||||
|
Closures or lambda expressions have types which cannot be named. However, they
|
||||||
|
implement special [`Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html),
|
||||||
|
[`FnMut`](https://doc.rust-lang.org/std/ops/trait.FnMut.html), and
|
||||||
|
[`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) traits:
|
||||||
|
|
||||||
|
The special type `fn` refers to function pointers - either the address of a
|
||||||
|
function, or a closure that captures nothing.
|
||||||
|
|
||||||
|
```rust,editable
|
||||||
|
fn apply_and_log(func: impl FnOnce(String) -> String, func_name: &str, input: &str) {
|
||||||
|
println!("Calling {func_name}({input}): {}", func(input.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let suffix = "-itis";
|
||||||
|
let add_suffix = |x| format!("{x}{suffix}");
|
||||||
|
apply_and_log(&add_suffix, "add_suffix", "senior");
|
||||||
|
apply_and_log(&add_suffix, "add_suffix", "appenix");
|
||||||
|
|
||||||
|
let mut v = Vec::new();
|
||||||
|
let mut accumulate = |x| {
|
||||||
|
v.push(x);
|
||||||
|
v.join("/")
|
||||||
|
};
|
||||||
|
apply_and_log(&mut accumulate, "accumulate", "red");
|
||||||
|
apply_and_log(&mut accumulate, "accumulate", "green");
|
||||||
|
apply_and_log(&mut accumulate, "accumulate", "blue");
|
||||||
|
|
||||||
|
let take_and_reverse = |mut prefix: String| {
|
||||||
|
prefix.push_str(&v.into_iter().rev().collect::<Vec<_>>().join("/"));
|
||||||
|
prefix
|
||||||
|
};
|
||||||
|
apply_and_log(take_and_reverse, "take_and_reverse", "reversed: ");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
An `Fn` (e.g. `add_suffix`) neither consumes nor mutates captured values. It can
|
||||||
|
be called needing only a shared reference to the closure, which means the
|
||||||
|
closure can be executed repeatedly and even concurrently.
|
||||||
|
|
||||||
|
An `FnMut` (e.g. `accumulate`) might mutate captured values. The closure object
|
||||||
|
is accessed via exclusive reference, so it can be called repeatedly but not
|
||||||
|
concurrently.
|
||||||
|
|
||||||
|
If you have an `FnOnce` (e.g. `take_and_reverse`), you may only call it once.
|
||||||
|
Doing so consumes the closure and any values captured by move.
|
||||||
|
|
||||||
|
`FnMut` is a subtype of `FnOnce`. `Fn` is a subtype of `FnMut` and `FnOnce`.
|
||||||
|
I.e. you can use an `FnMut` wherever an `FnOnce` is called for, and you can use
|
||||||
|
an `Fn` wherever an `FnMut` or `FnOnce` is called for.
|
||||||
|
|
||||||
|
When you define a function that takes a closure, you should take `FnOnce` if you
|
||||||
|
can (i.e. you call it once), or `FnMut` else, and last `Fn`. This allows the
|
||||||
|
most flexibility for the caller.
|
||||||
|
|
||||||
|
In contrast, when you have a closure, the most flexible you can have is `Fn`
|
||||||
|
(which can be passed to a consumer of any of the 3 closure traits), then
|
||||||
|
`FnMut`, and lastly `FnOnce`.
|
||||||
|
|
||||||
|
The compiler also infers `Copy` (e.g. for `add_suffix`) and `Clone` (e.g.
|
||||||
|
`take_and_reverse`), depending on what the closure captures. Function pointers
|
||||||
|
(references to `fn` items) implement `Copy` and `Fn`.
|
||||||
|
|
||||||
|
</details>
|
@ -1,80 +0,0 @@
|
|||||||
---
|
|
||||||
minutes: 10
|
|
||||||
---
|
|
||||||
|
|
||||||
# Closures
|
|
||||||
|
|
||||||
Closures or lambda expressions have types which cannot be named. However, they
|
|
||||||
implement special [`Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html),
|
|
||||||
[`FnMut`](https://doc.rust-lang.org/std/ops/trait.FnMut.html), and
|
|
||||||
[`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) traits:
|
|
||||||
|
|
||||||
```rust,editable
|
|
||||||
fn apply_and_log(func: impl FnOnce(i32) -> i32, func_name: &str, input: i32) {
|
|
||||||
println!("Calling {func_name}({input}): {}", func(input))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let n = 3;
|
|
||||||
let add_3 = |x| x + n;
|
|
||||||
apply_and_log(&add_3, "add_3", 10);
|
|
||||||
apply_and_log(&add_3, "add_3", 20);
|
|
||||||
|
|
||||||
let mut v = Vec::new();
|
|
||||||
let mut accumulate = |x: i32| {
|
|
||||||
v.push(x);
|
|
||||||
v.iter().sum::<i32>()
|
|
||||||
};
|
|
||||||
apply_and_log(&mut accumulate, "accumulate", 4);
|
|
||||||
apply_and_log(&mut accumulate, "accumulate", 5);
|
|
||||||
|
|
||||||
let multiply_sum = |x| x * v.into_iter().sum::<i32>();
|
|
||||||
apply_and_log(multiply_sum, "multiply_sum", 3);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
An `Fn` (e.g. `add_3`) neither consumes nor mutates captured values. It can be
|
|
||||||
called needing only a shared reference to the closure, which means the closure
|
|
||||||
can be executed repeatedly and even concurrently.
|
|
||||||
|
|
||||||
An `FnMut` (e.g. `accumulate`) might mutate captured values. The closure object
|
|
||||||
is accessed via exclusive reference, so it can be called repeatedly but not
|
|
||||||
concurrently.
|
|
||||||
|
|
||||||
If you have an `FnOnce` (e.g. `multiply_sum`), you may only call it once. Doing
|
|
||||||
so consumes the closure and any values captured by move.
|
|
||||||
|
|
||||||
`FnMut` is a subtype of `FnOnce`. `Fn` is a subtype of `FnMut` and `FnOnce`.
|
|
||||||
I.e. you can use an `FnMut` wherever an `FnOnce` is called for, and you can use
|
|
||||||
an `Fn` wherever an `FnMut` or `FnOnce` is called for.
|
|
||||||
|
|
||||||
When you define a function that takes a closure, you should take `FnOnce` if you
|
|
||||||
can (i.e. you call it once), or `FnMut` else, and last `Fn`. This allows the
|
|
||||||
most flexibility for the caller.
|
|
||||||
|
|
||||||
In contrast, when you have a closure, the most flexible you can have is `Fn`
|
|
||||||
(which can be passed to a consumer of any of the 3 closure traits), then
|
|
||||||
`FnMut`, and lastly `FnOnce`.
|
|
||||||
|
|
||||||
The compiler also infers `Copy` (e.g. for `add_3`) and `Clone` (e.g.
|
|
||||||
`multiply_sum`), depending on what the closure captures. Function pointers
|
|
||||||
(references to `fn` items) implement `Copy` and `Fn`.
|
|
||||||
|
|
||||||
By default, closures will capture each variable from an outer scope by the least
|
|
||||||
demanding form of access they can (by shared reference if possible, then
|
|
||||||
exclusive reference, then by move). The `move` keyword forces capture by value.
|
|
||||||
|
|
||||||
```rust,editable
|
|
||||||
fn make_greeter(prefix: String) -> impl Fn(&str) {
|
|
||||||
return move |name| println!("{} {}", prefix, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let hi = make_greeter("Hi".to_string());
|
|
||||||
hi("Greg");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
@ -15,6 +15,7 @@ system:
|
|||||||
- Traits: behaviors shared by multiple types.
|
- Traits: behaviors shared by multiple types.
|
||||||
- Generics: parameterizing types on other types.
|
- Generics: parameterizing types on other types.
|
||||||
- Standard library types and traits: a tour of Rust's rich standard library.
|
- Standard library types and traits: a tour of Rust's rich standard library.
|
||||||
|
- Closures: function pointers with data.
|
||||||
|
|
||||||
## Schedule
|
## Schedule
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user