From b3adc3b09a9383a50569dc94e0c7ac386820cad6 Mon Sep 17 00:00:00 2001 From: Frances Wingerter <91758128+fw-immunant@users.noreply.github.com> Date: Sun, 8 Sep 2024 13:49:54 +0000 Subject: [PATCH] Clarify Fn traits slide (#2333) Improve naming and always capture something in our closures; explain other details/variations in speaker notes. Fixes #2251. --- src/std-traits/closures.md | 45 +++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/std-traits/closures.md b/src/std-traits/closures.md index cd7c013f..cdd315d2 100644 --- a/src/std-traits/closures.md +++ b/src/std-traits/closures.md @@ -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::() }; - 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::(); - println!("multiply_sum: {}", apply_with_log(multiply_sum, 3)); + apply_and_log(multiply_sum, "multiply_sum", 3); } ```
-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. +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. 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 @@ -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 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 (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) {