1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-05-14 14:46:43 +02:00

Clarify Fn traits slide (#2333)

Improve naming and always capture something in our closures; explain
other details/variations in speaker notes.

Fixes #2251.
This commit is contained in:
Frances Wingerter 2024-09-08 13:49:54 +00:00 committed by GitHub
parent 432f7bc5dc
commit b3adc3b09a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -10,39 +10,41 @@ implement special [`Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html),
[`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) traits: [`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) traits:
```rust,editable ```rust,editable
fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 { fn apply_and_log(func: impl FnOnce(i32) -> i32, func_name: &str, input: i32) {
println!("Calling function on {input}"); println!("Calling {func_name}({input}): {}", func(input))
func(input)
} }
fn main() { fn main() {
let add_3 = |x| x + 3; let n = 3;
println!("add_3: {}", apply_with_log(add_3, 10)); let add_3 = |x| x + n;
println!("add_3: {}", apply_with_log(add_3, 20)); apply_and_log(&add_3, "add_3", 10);
apply_and_log(&add_3, "add_3", 20);
let mut v = Vec::new(); let mut v = Vec::new();
let mut accumulate = |x: i32| { let mut accumulate = |x: i32| {
v.push(x); v.push(x);
v.iter().sum::<i32>() v.iter().sum::<i32>()
}; };
println!("accumulate: {}", apply_with_log(&mut accumulate, 4)); apply_and_log(&mut accumulate, "accumulate", 4);
println!("accumulate: {}", apply_with_log(&mut accumulate, 5)); apply_and_log(&mut accumulate, "accumulate", 5);
let multiply_sum = |x| x * v.into_iter().sum::<i32>(); let multiply_sum = |x| x * v.into_iter().sum::<i32>();
println!("multiply_sum: {}", apply_with_log(multiply_sum, 3)); apply_and_log(multiply_sum, "multiply_sum", 3);
} }
``` ```
<details> <details>
An `Fn` (e.g. `add_3`) neither consumes nor mutates captured values, or perhaps An `Fn` (e.g. `add_3`) neither consumes nor mutates captured values. It can be
captures nothing at all. It can be called multiple times concurrently. 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. You can call it An `FnMut` (e.g. `accumulate`) might mutate captured values. The closure object
multiple times, but not concurrently. 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. It If you have an `FnOnce` (e.g. `multiply_sum`), you may only call it once. Doing
might consume captured values. so consumes the closure and any values captured by move.
`FnMut` is a subtype of `FnOnce`. `Fn` is a subtype of `FnMut` and `FnOnce`. `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 I.e. you can use an `FnMut` wherever an `FnOnce` is called for, and you can use
@ -52,14 +54,17 @@ 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 can (i.e. you call it once), or `FnMut` else, and last `Fn`. This allows the
most flexibility for the caller. most flexibility for the caller.
In contrast, when you have a closure, the most flexible you can have is `Fn` (it In contrast, when you have a closure, the most flexible you can have is `Fn`
can be passed everywhere), then `FnMut`, and lastly `FnOnce`. (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. The compiler also infers `Copy` (e.g. for `add_3`) and `Clone` (e.g.
`multiply_sum`), depending on what the closure captures. `multiply_sum`), depending on what the closure captures. Function pointers
(references to `fn` items) implement `Copy` and `Fn`.
By default, closures will capture by reference if they can. The `move` keyword By default, closures will capture each variable from an outer scope by the least
makes them capture by value. 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 ```rust,editable
fn make_greeter(prefix: String) -> impl Fn(&str) { fn make_greeter(prefix: String) -> impl Fn(&str) {