Skip to content

Commit

Permalink
clarify Fn traits slide
Browse files Browse the repository at this point in the history
improve naming and always capture something in our closures; explain other details/variations in speaker notes
  • Loading branch information
fw-immunant committed Aug 30, 2024
1 parent da9f57a commit 2d78b7f
Showing 1 changed file with 23 additions and 19 deletions.
42 changes: 23 additions & 19 deletions src/std-traits/closures.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```rust,editable
fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 {
println!("Calling function on {input}");
func(input)
fn apply_and_log(func: impl FnOnce(i32) -> i32, func_name: &str, input: i32) {
println!("Calling {func_name}({input}): {}", func(input))
}
fn main() {
let add_3 = |x| x + 3;
println!("add_3: {}", apply_with_log(add_3, 10));
println!("add_3: {}", apply_with_log(add_3, 20));
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>()
};
println!("accumulate: {}", apply_with_log(&mut accumulate, 4));
println!("accumulate: {}", apply_with_log(&mut accumulate, 5));
apply_and_log(&mut accumulate, "accumulate", 4);
apply_and_log(&mut accumulate, "accumulate", 5);
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>

An `Fn` (e.g. `add_3`) neither consumes nor mutates captured values, or perhaps
captures nothing at all. It can be called multiple times concurrently.
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. You can call it
multiple times, but not concurrently.
multiple times, but not concurrently (because doing so would require having
multiple exclusive references to the closure at once).

If you have an `FnOnce` (e.g. `multiply_sum`), you may only call it once. It
might consume captured values.
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
Expand All @@ -52,14 +54,16 @@ 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` (it
can be passed everywhere), then `FnMut`, and lastly `FnOnce`.
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.
`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
makes them capture by value.
By default, closures will capture each variable from an outer scope by the least
demanding form of access they can. The `move` keyword forces capture by value.

```rust,editable
fn make_greeter(prefix: String) -> impl Fn(&str) {
Expand Down

0 comments on commit 2d78b7f

Please sign in to comment.