From 6d68a7f454b8369e3b7d54ba52036e968ee2b5f6 Mon Sep 17 00:00:00 2001 From: Roxane Date: Tue, 6 Jul 2021 12:41:44 -0400 Subject: [PATCH 01/11] Update closure types documentation so it includes information about RFC2229 --- src/types/closure.md | 306 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 256 insertions(+), 50 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index 0f59f3f64..e6f70f6b1 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -4,38 +4,46 @@ r[type.closure] A [closure expression] produces a closure value with a unique, anonymous type that cannot be written out. A closure type is approximately equivalent to a -struct which contains the captured variables. For instance, the following +struct which contains the captured values. For instance, the following closure: ```rust +#[derive(Debug)] +struct Point { x:i32, y:i32 } +struct Rectangle { left_top: Point, right_bottom: Point } + fn f String> (g: F) { println!("{}", g()); } -let mut s = String::from("foo"); -let t = String::from("bar"); - -f(|| { - s += &t; - s -}); -// Prints "foobar". +let mut rect = Rectangle { + left_top: Point { x: 1, y: 1 }, + right_bottom: Point { x: 0, y: 0 } +}; + +let c = || { + rect.left_top.x += 1; + rect.right_bottom.x += 1; + format!("{:?}", rect.left_top) +}; +// Prints "Point { x: 2, y: 1 }". ``` generates a closure type roughly like the following: - + ```rust,ignore struct Closure<'a> { - s : String, - t : &'a String, + left_top : &'a mut Point, + right_bottom_x : &'a mut i32, } impl<'a> FnOnce<()> for Closure<'a> { type Output = String; fn call_once(self) -> String { - self.s += &*self.t; - self.s + self.left_top.x += 1; + self.right_bottom_x += 1; + format!("{:?}", self.left_top) } } ``` @@ -44,7 +52,7 @@ so that the call to `f` works as if it were: ```rust,ignore -f(Closure{s: s, t: &t}); +f(Closure{ left_top: rect.left_top, right_bottom_x: rect.left_top.x }); ``` ## Capture modes @@ -52,46 +60,145 @@ f(Closure{s: s, t: &t}); r[type.closure.capture] r[type.closure.capture.order] -The compiler prefers to capture a closed-over variable by immutable borrow, +The compiler prefers to capture a value by immutable borrow, followed by unique immutable borrow (see below), by mutable borrow, and finally -by move. It will pick the first choice of these that is compatible with how the -captured variable is used inside the closure body. The compiler does not take -surrounding code into account, such as the lifetimes of involved variables, or -of the closure itself. +by move. It will pick the first choice of these that allows the closure to +compile. The choice is made only with regards to the contents of the closure +expression; the compiler does not take into account surrounding code, such as +the lifetimes of involved variables or fields. +>>>>>>> 881f305... Update closure types documentation so it includes information about RFC2229 -r[type.closure.capture.move] -If the `move` keyword is used, then all captures are by move or, for `Copy` -types, by copy, regardless of whether a borrow would work. The `move` keyword is -usually used to allow the closure to outlive the captured values, such as if the -closure is being returned or used to spawn a new thread. +## Capture Precision -r[type.closure.capture.composite] -Composite types such as structs, tuples, and enums are always captured entirely, -not by individual fields. It may be necessary to borrow into a local variable in -order to capture a single field: - +The precise path that gets captured is typically the full path that is used in the closure, but there are cases where we will only capture a prefix of the path. + + +### Shared prefix + +In the case where a path and one of the ancestor’s of that path are both captured by a closure, the ancestor path is captured with the highest capture mode among the two captures,`CaptureMode = max(AncestorCaptureMode, DescendantCaptureMode)`, using the strict weak ordering + +`ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue`. + +Note that this might need to be applied recursively. + +```rust= +let s = String::new("S"); +let t = (s, String::new("T")); +let mut u = (t, String::new("U")); + +let c = || { + println!("{:?}", u); // u captured by ImmBorrow + u.0.truncate(0); // u.0 captured by MutBorrow + move_value(u.0.0); // u.0.0 captured by ByValue +}; +``` + +Overall the closure will capture `u` by `ByValue`. + +### Wild Card Patterns +Closures only capture data that needs to be read, which means the following closures will not capture `x` ```rust -# use std::collections::HashSet; -# -struct SetVec { - set: HashSet, - vec: Vec -} +let x = 10; +let c = || { + let _ = x; +}; + +let c = || match x { + _ => println!("Hello World!") +}; +``` -impl SetVec { - fn populate(&mut self) { - let vec = &mut self.vec; - self.set.iter().for_each(|&n| { - vec.push(n); - }) - } -} +### Capturing references in move contexts + +Rust doesn't allow moving fields out of references. As a result, in the case of move closures, when values accessed through a shared references are moved into the closure body, the compiler, instead of moving the values out of the reference, would reborrow the data. + +```rust +struct T(String, String); + +let mut t = T(String::from("foo"), String::from("bar")); +let t = &mut t; +let c = move || t.0.truncate(0); // closure captures (&mut t.0) ``` -If, instead, the closure were to use `self.vec` directly, then it would attempt -to capture `self` by mutable reference. But since `self.set` is already -borrowed to iterate over, the code would not compile. +### Raw pointer dereference +In Rust, it's `unsafe` to dereference a raw pointer. Therefore, closures will only capture the prefix of a path that runs up to, but not including, the first dereference of a raw pointer. + +```rust, +struct T(String, String); + +let t = T(String::from("foo"), String::from("bar")); +let t = &t as *const T; + +let c = || unsafe { + println!("{}", (*t).0); // closure captures t +}; +``` + +### Reference into unaligned `struct`s + +In Rust, it's `unsafe` to hold references to unaligned fields in a structure, and therefore, closures will only capture the prefix of the path that runs up to, but not including, the first field access into an unaligned structure. + +```rust +#[repr(packed)] +struct T(String, String); + +let t = T(String::from("foo"), String::from("bar")); +let c = || unsafe { + println!("{}", t.0); // closure captures t +}; +``` + + +### `Box` vs other `Deref` implementations + +The compiler treats the implementation of the Deref trait for `Box` differently, as it is considered a special entity. + +For example, let us look at examples involving `Rc` and `Box`. The `*rc` is desugared to a call to the trait method `deref` defined on `Rc`, but since `*box` is treated differently by the compiler, the compiler is able to do precise capture on contents of the `Box`. + +#### Non `move` closure + +In a non `move` closure, if the contents of the `Box` are not moved into the closure body, the contents of the `Box` are precisely captured. + +```rust +# use std::rc::Rc; + +struct S(i32); + +let b = Box::new(S(10)); +let c_box = || { + println!("{}", (*b).0); // closure captures `(*b).0` +}; + +let r = Rc::new(S(10)); +let c_rc = || { + println!("{}", (*r).0); // closure caprures `r` +}; +``` + +However, if the contents of the `Box` are moved into the closure, then the box is entirely captured. This is done so the amount of data that needs to be moved into the closure is minimized. + +```rust +struct S(i32); + +let b = Box::new(S(10)); +let c_box = || { + let x = (*b).0; // closure captures `b` +}; +``` + +#### `move` closure + +Similarly to moving contents of a `Box` in a non-`move` closure, reading the contents of a `Box` in a `move` closure will capture the `Box` entirely. + +```rust +struct S(i32); + +let b = Box::new(S(10)); +let c_box = || { + println!("{}", (*b).0); // closure captures `b` +}; +``` ## Unique immutable borrows in captures @@ -123,6 +230,7 @@ the declaration of `y` will produce an error because it would violate the uniqueness of the closure's borrow of `x`; the declaration of z is valid because the closure's lifetime has expired at the end of the block, releasing the borrow. + ## Call traits and coercions r[type.closure.call] @@ -176,12 +284,13 @@ following traits if allowed to do so by the types of the captures it stores: r[type.closure.traits.behavior] The rules for [`Send`] and [`Sync`] match those for normal struct types, while [`Clone`] and [`Copy`] behave as if [derived]. For [`Clone`], the order of -cloning of the captured variables is left unspecified. +cloning of the captured values is left unspecified. + Because captures are often by reference, the following general rules arise: -* A closure is [`Sync`] if all captured variables are [`Sync`]. -* A closure is [`Send`] if all variables captured by non-unique immutable +* A closure is [`Sync`] if all captured values are [`Sync`]. +* A closure is [`Send`] if all values captured by non-unique immutable reference are [`Sync`], and all values captured by unique immutable or mutable reference, copy, or move are [`Send`]. * A closure is [`Clone`] or [`Copy`] if it does not capture any values by @@ -195,3 +304,100 @@ Because captures are often by reference, the following general rules arise: [`Sync`]: ../special-types-and-traits.md#sync [closure expression]: ../expressions/closure-expr.md [derived]: ../attributes/derive.md + +## Drop Order + +If a closure captures a field of a composite types such as structs, tuples, and enums by value, the field's lifetime would now be tied to the closure. As a result, it is possible for disjoint fields of a composite types to be dropped at different times. + +```rust +{ + let tuple = + (String::from("foo"), String::from("bar")); // --+ + { // | + let c = || { // ----------------------------+ | + // tuple.0 is captured into the closure | | + drop(tuple.0); // | | + }; // | | + } // 'c' and 'tuple.0' dropped here ------------+ | +} // tuple.1 dropped here -----------------------------+ +``` + +# Edition 2018 and before + +## Closure types difference + +In Edition 2018 and before, a closure would capture variables in its entirety. This means that for the example used in the [Closure types](#closure-types) section, the generated closure type would instead look something like this: + + +```rust,ignore +struct Closure<'a> { + rect : &'a mut Rectangle, +} + +impl<'a> FnOnce<()> for Closure<'a> { + type Output = String; + fn call_once(self) -> String { + self.rect.left_top.x += 1; + self.rect.right_bottom.x += 1; + format!("{:?}", self.rect.left_top) + } +} +``` +and the call to `f` would work as follows: + +```rust,ignore +f(Closure { rect: rect }); +``` + +## Capture precision difference + +r[type.closure.capture.composite] +Composite types such as structs, tuples, and enums are always captured in its intirety, +not by individual fields. As a result, it may be necessary to borrow into a local variable in order to capture a single field: + +```rust +# use std::collections::HashSet; +# +struct SetVec { + set: HashSet, + vec: Vec +} + +impl SetVec { + fn populate(&mut self) { + let vec = &mut self.vec; + self.set.iter().for_each(|&n| { + vec.push(n); + }) + } +} +``` + +If, instead, the closure were to use `self.vec` directly, then it would attempt +to capture `self` by mutable reference. But since `self.set` is already +borrowed to iterate over, the code would not compile. + +r[type.closure.capture.move] +If the `move` keyword is used, then all captures are by move or, for `Copy` +types, by copy, regardless of whether a borrow would work. The `move` keyword is +usually used to allow the closure to outlive the captured values, such as if the +closure is being returned or used to spawn a new thread. + +Regardless of if the data will be read by the closure, i.e. in case of wild card patterns, if a variable defined outside the closure is mentioned within the closure the variable will be captured in its entirety. + +## Drop order difference + +As composite types are captured in their entirety, a closure which captures one of those composite types by value would drop the entire captured variable at the same time as the closure gets dropped. + +```rust +{ + let tuple = + (String::from("foo"), String::from("bar")); + { + let c = || { // --------------------------+ + // tuple is captured into the closure | + drop(tuple.0); // | + }; // | + } // 'c' and 'tuple' dropped here ------------+ +} +``` From 5fb1c02d36e7fda9c5f979006001794ff4913fe9 Mon Sep 17 00:00:00 2001 From: Roxane Date: Thu, 8 Jul 2021 20:10:43 -0400 Subject: [PATCH 02/11] Address comments --- src/types/closure.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index e6f70f6b1..27951db08 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -81,14 +81,15 @@ In the case where a path and one of the ancestor’s of that path are both captu Note that this might need to be applied recursively. -```rust= -let s = String::new("S"); -let t = (s, String::new("T")); -let mut u = (t, String::new("U")); +```rust +# fn move_value(_: T){} +let s = String::from("S"); +let t = (s, String::from("T")); +let mut u = (t, String::from("U")); let c = || { println!("{:?}", u); // u captured by ImmBorrow - u.0.truncate(0); // u.0 captured by MutBorrow + u.1.truncate(0); // u.0 captured by MutBorrow move_value(u.0.0); // u.0.0 captured by ByValue }; ``` @@ -111,7 +112,7 @@ let c = || match x { ### Capturing references in move contexts -Rust doesn't allow moving fields out of references. As a result, in the case of move closures, when values accessed through a shared references are moved into the closure body, the compiler, instead of moving the values out of the reference, would reborrow the data. +Moving fields out of references is not allowed. As a result, in the case of move closures, when values accessed through a shared references are moved into the closure body, the compiler, instead of moving the values out of the reference, would reborrow the data. ```rust struct T(String, String); @@ -122,7 +123,7 @@ let c = move || t.0.truncate(0); // closure captures (&mut t.0) ``` ### Raw pointer dereference -In Rust, it's `unsafe` to dereference a raw pointer. Therefore, closures will only capture the prefix of a path that runs up to, but not including, the first dereference of a raw pointer. +Because it is `unsafe` to dereference a raw pointer, closures will only capture the prefix of a path that runs up to, but not including, the first dereference of a raw pointer. ```rust, struct T(String, String); @@ -137,7 +138,7 @@ let c = || unsafe { ### Reference into unaligned `struct`s -In Rust, it's `unsafe` to hold references to unaligned fields in a structure, and therefore, closures will only capture the prefix of the path that runs up to, but not including, the first field access into an unaligned structure. +Because it is `unsafe` to hold references to unaligned fields in a structure, closures will only capture the prefix of the path that runs up to, but not including, the first field access into an unaligned structure. ```rust #[repr(packed)] @@ -152,10 +153,13 @@ let c = || unsafe { ### `Box` vs other `Deref` implementations -The compiler treats the implementation of the Deref trait for `Box` differently, as it is considered a special entity. +The implementation of the [`Deref`] trait for [`Box`] is treated differently from other `Deref` implementations, as it is considered a special entity. For example, let us look at examples involving `Rc` and `Box`. The `*rc` is desugared to a call to the trait method `deref` defined on `Rc`, but since `*box` is treated differently by the compiler, the compiler is able to do precise capture on contents of the `Box`. +[`Box`]: ../special-types-and-traits.md#boxt +[`Deref`]: ../special-types-and-traits.md#deref-and-derefmut + #### Non `move` closure In a non `move` closure, if the contents of the `Box` are not moved into the closure body, the contents of the `Box` are precisely captured. @@ -352,7 +356,7 @@ f(Closure { rect: rect }); ## Capture precision difference r[type.closure.capture.composite] -Composite types such as structs, tuples, and enums are always captured in its intirety, +Composite types such as structs, tuples, and enums are always captured in its entirety, not by individual fields. As a result, it may be necessary to borrow into a local variable in order to capture a single field: ```rust From 1e2a69c8c3970c0a1adc6b918c7f835608128059 Mon Sep 17 00:00:00 2001 From: Aman Arora Date: Tue, 20 Jul 2021 04:10:29 -0400 Subject: [PATCH 03/11] Copy capture analyis alogrithm from hackmd --- src/types/closure.md | 133 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/src/types/closure.md b/src/types/closure.md index 27951db08..e1bec967d 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -326,6 +326,139 @@ If a closure captures a field of a composite types such as structs, tuples, and } // tuple.1 dropped here -----------------------------+ ``` + +## Overall Capture analysis algorithm + +* Input: + * Analyzing the closure C yields a set of `(Mode, Place)` pairs that are accessed + * Access mode is `ref`, `ref uniq`, `ref mut`, or `by-value` (ordered least to max) + * Closure mode is `ref` or `move` +* Output: + * Minimal `(Mode, Place)` pairs that are actually captured +* Cleanup and truncation + * Generate C' by mapping each (Mode, Place) in C: + * `(Mode1, Place1) = ref_opt(unsafe_check(copy_type(Mode, Place)))` + * if this is a ref closure: + * Add `ref_xform(Mode1, Place1)` to C' + * else: + * Add `move_xform(Mode1, Place1)` to C' +* Minimization + * Until no rules apply: + * For each two places (M1, P1), (M2, P2) where P1 is a prefix of P2: + * Remove both places from the set + * Add (max(M1, M2), P1) into the set +* Helper functions: + * `copy_type(Mode, Place) -> (Mode, Place)` + * "By-value use of a copy type is a ref" + * If Mode = "by-value" and type(Place) is `Copy`: + * Return (ref, Place) + * Else + * Return (Mode, Place) + * `unsafe_check(Mode, Place) -> (Mode, Place)` + * "Ensure unsafe accesses occur within the closure" + * If Place contains a deref of a raw pointer: + * Let Place1 = Place truncated just before the deref + * Return (Mode, Place1) + * If Mode is `ref *` and the place contains a field of a packed struct: + * Let Place1 = Place truncated just before the field + * Return (Mode, Place1) + * Else + * Return (Mode, Place1) + * `move_xform(Mode, Place) -> (Mode, Place)` (For move closures) + * "Take ownership if data being accessed is owned by the variable used to access it (or if closure attempts to move data that it doesn't own)." + * "When taking ownership, only capture data found on the stack." + * "Otherwise, reborrow the reference." + * If Mode is `ref mut` and the place contains a deref of an `&mut`: + * Return (Mode, Place) + * Else if Mode is `ref *` and the place contains a deref of an `&`: + * Return (Mode, Place) + * Else if place contains a deref: + * Let Place1 = Place truncated just before the deref + * Return (ByValue, Place1) + * Else: + * Return (ByValue, Place) + * `ref_xform(Mode, Place) -> (Mode, Place)` (for ref closures) + * "If taking ownership of data, only move data from enclosing stack frame." + * Generate C' by mapping each (Mode, Place) in C + * If Mode is ByValue and place contains a deref: + * Let Place1 = Place truncated just before the deref + * Return (ByValue, Place1) + * Else: + * Return (Mode, Place) + * `ref_opt(Mode, Place) -> (Mode, Place)` (for ref closures) + * "Optimization: borrow the ref, not data owned by ref." + * If Place contains a deref of an `&`... + * ...or something + +## Key examples + +### box-mut + +```rust +fn box_mut() { + let mut s = Foo { x: 0 } ; + + let px = &mut s; + let bx = Box::new(px); + + + let c = #[rustc_capture_analysis] move || bx.x += 10; + // Mutable reference to this place: + // (*(*bx)).x + // ^ ^ + // | a Box + // a &mut +} +``` + +``` +Closure mode = move +C = { + (ref mut, (*(*bx)).x) +} +C' = C +``` + +Output is the same: `C' = C` + +### Packed-field-ref-and-move + +When you have a closure that both references a packed field (which is unsafe) and moves from it (which is safe) we capture the entire struct, rather than just moving the field. This is to aid in predictability, so that removing the move doesn't make the closure become unsafe: + +```rust +print(&packed.x); +move_value(packed.x); +``` + + +```rust +struct Point { x: i32, y: i32 } +fn f(p: &Point) -> impl Fn() { + let c = move || { + let x = p.x; + }; + + // x.x -> ByValue + // after rules x -> ByValue + + c +} + +struct Point { x: i32, y: i32 } +fn g(p: &mut Point) -> impl Fn() { + let c = move || { + let x = p.x; // ought to: (ref, (*p).x) + }; + + move || { + p.y += 1; + } + + + // x.x -> ByValue + +``` + # Edition 2018 and before ## Closure types difference From cfa1467dc57be355b973e764924d5260897bc002 Mon Sep 17 00:00:00 2001 From: Aman Arora Date: Tue, 20 Jul 2021 04:46:00 -0400 Subject: [PATCH 04/11] Update copy type, optimization, make algorithm (place, mode) --- src/types/closure.md | 137 ++++++++++++++++++++++++++----------------- 1 file changed, 82 insertions(+), 55 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index e1bec967d..a7a9168f9 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -330,71 +330,77 @@ If a closure captures a field of a composite types such as structs, tuples, and ## Overall Capture analysis algorithm * Input: - * Analyzing the closure C yields a set of `(Mode, Place)` pairs that are accessed + * Analyzing the closure C yields a mapping of `Place -> Mode` that are accessed * Access mode is `ref`, `ref uniq`, `ref mut`, or `by-value` (ordered least to max) + * For a `Place` that is used in two different acess modes within the same closure, the mode reported from closure analysis is the maximum access mode. + * Note: `ByValue` use of a `Copy` type is seen as a `ref` access mode. * Closure mode is `ref` or `move` * Output: - * Minimal `(Mode, Place)` pairs that are actually captured + * Minimal `(Place, Mode)` pairs that are actually captured * Cleanup and truncation * Generate C' by mapping each (Mode, Place) in C: - * `(Mode1, Place1) = ref_opt(unsafe_check(copy_type(Mode, Place)))` + * `(Place1, Mode1) = ref_opt(unsafe_check(Place, Mode))` * if this is a ref closure: - * Add `ref_xform(Mode1, Place1)` to C' + * Add `ref_xform(Place1, Mode1)` to C' * else: - * Add `move_xform(Mode1, Place1)` to C' + * Add `move_xform(Place1, Mode1)` to C' * Minimization * Until no rules apply: - * For each two places (M1, P1), (M2, P2) where P1 is a prefix of P2: + * For each two places (P1, M1), (P2, M2) where P1 is a prefix of P2: * Remove both places from the set - * Add (max(M1, M2), P1) into the set + * Add (P1, max(M1, M2)) into the set * Helper functions: - * `copy_type(Mode, Place) -> (Mode, Place)` - * "By-value use of a copy type is a ref" - * If Mode = "by-value" and type(Place) is `Copy`: - * Return (ref, Place) - * Else - * Return (Mode, Place) - * `unsafe_check(Mode, Place) -> (Mode, Place)` + * `unsafe_check(Place, Mode) -> (Place, Mode)` * "Ensure unsafe accesses occur within the closure" * If Place contains a deref of a raw pointer: * Let Place1 = Place truncated just before the deref - * Return (Mode, Place1) + * Return (Place1, Mode) * If Mode is `ref *` and the place contains a field of a packed struct: * Let Place1 = Place truncated just before the field - * Return (Mode, Place1) + * Return (Place1, Mode) * Else - * Return (Mode, Place1) - * `move_xform(Mode, Place) -> (Mode, Place)` (For move closures) + * Return (Place, Mode) + * `move_xform(Place, Mode) -> (Place, Mode)` (For move closures) * "Take ownership if data being accessed is owned by the variable used to access it (or if closure attempts to move data that it doesn't own)." * "When taking ownership, only capture data found on the stack." * "Otherwise, reborrow the reference." * If Mode is `ref mut` and the place contains a deref of an `&mut`: - * Return (Mode, Place) + * Return (Place, Mode) * Else if Mode is `ref *` and the place contains a deref of an `&`: - * Return (Mode, Place) + * Return (Place, Mode) * Else if place contains a deref: * Let Place1 = Place truncated just before the deref - * Return (ByValue, Place1) + * Return (Place1, ByValue) * Else: - * Return (ByValue, Place) - * `ref_xform(Mode, Place) -> (Mode, Place)` (for ref closures) + * Return (Place, ByValue) + * `ref_xform(Place, Mode) -> (Place, Mode)` (for ref closures) * "If taking ownership of data, only move data from enclosing stack frame." * Generate C' by mapping each (Mode, Place) in C * If Mode is ByValue and place contains a deref: * Let Place1 = Place truncated just before the deref - * Return (ByValue, Place1) + * Return (Place1, ByValue) * Else: - * Return (Mode, Place) - * `ref_opt(Mode, Place) -> (Mode, Place)` (for ref closures) + * Return (Place, Mode) + * `ref_opt(Place, Mode) -> (Place, Mode)` * "Optimization: borrow the ref, not data owned by ref." - * If Place contains a deref of an `&`... - * ...or something + * Disjoint capture over immutable reference doesn't add too much value because the fields can either be borrowed immutably or copied. + * Edge case: Field that is accessed via the referece lives longer than the reference. + * Resolution: Only consider the last Deref + * If Place is (Base, Projections), where Projections is a list of size N. + * For all `i, 0 <= i < N`, Projections[i] != Deref + * Return (Place, Mode) + * If `l, 0 <= l < N` is the last/rightmost Deref Projection i.e. for any `i, l < i < N` Projection[i] != Deref, + and `Place.type_before_projection(l) = ty::Ref(.., Mutability::Not)` + * Let Place1 = (Base, Projections[0..=l]) + * Return (Place1, Ref) ## Key examples ### box-mut ```rust +struct Foo { x: i32 } + fn box_mut() { let mut s = Foo { x: 0 } ; @@ -402,7 +408,7 @@ fn box_mut() { let bx = Box::new(px); - let c = #[rustc_capture_analysis] move || bx.x += 10; + let c = move || bx.x += 10; // Mutable reference to this place: // (*(*bx)).x // ^ ^ @@ -411,7 +417,8 @@ fn box_mut() { } ``` -``` + +```ignore Closure mode = move C = { (ref mut, (*(*bx)).x) @@ -426,37 +433,57 @@ Output is the same: `C' = C` When you have a closure that both references a packed field (which is unsafe) and moves from it (which is safe) we capture the entire struct, rather than just moving the field. This is to aid in predictability, so that removing the move doesn't make the closure become unsafe: ```rust -print(&packed.x); -move_value(packed.x); +#[repr(packed)] +struct Packed { x: String } + +# fn use_ref(_: &T) {} +# fn move_value(_: T) {} + +fn main() { + let packed = Packed { x: String::new() }; + + let c = || { + use_ref(&packed.x); + move_value(packed.x); + }; + + c(); +} +``` + + +```ignore +Closure mode = ref +C = { + (ref mut, packed) +} +C' = C ``` +### Optimization-Edge-Case +```edition2021 +struct Int(i32); +struct B<'a>(&'a i32); -```rust -struct Point { x: i32, y: i32 } -fn f(p: &Point) -> impl Fn() { - let c = move || { - let x = p.x; - }; - - // x.x -> ByValue - // after rules x -> ByValue +struct MyStruct<'a> { + a: &'static Int, + b: B<'a>, +} +fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static { + let c = || drop(&m.a.0); c -} +} -struct Point { x: i32, y: i32 } -fn g(p: &mut Point) -> impl Fn() { - let c = move || { - let x = p.x; // ought to: (ref, (*p).x) - }; - - move || { - p.y += 1; - } - - - // x.x -> ByValue - +``` + + +```ignore +Closure mode = ref +C = { + (ref mut, *m.a) +} +C' = C ``` # Edition 2018 and before From 3b222a26bb66d6f56f6ac6775d3afa8812ccc10c Mon Sep 17 00:00:00 2001 From: Roxane Date: Tue, 20 Jul 2021 14:41:15 -0400 Subject: [PATCH 05/11] fix stray merge conflict --- src/types/closure.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index a7a9168f9..d4519cbc2 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -62,11 +62,10 @@ r[type.closure.capture] r[type.closure.capture.order] The compiler prefers to capture a value by immutable borrow, followed by unique immutable borrow (see below), by mutable borrow, and finally -by move. It will pick the first choice of these that allows the closure to -compile. The choice is made only with regards to the contents of the closure -expression; the compiler does not take into account surrounding code, such as -the lifetimes of involved variables or fields. ->>>>>>> 881f305... Update closure types documentation so it includes information about RFC2229 +by move. It will pick the first choice of these that is compatible with how the +captured value is used inside the closure body. The compiler does not take +surrounding code into account, such as the lifetimes of involved variables or fields, or +of the closure itself. ## Capture Precision From ac26129e43170f218bb54937c366ea16ff3a30f3 Mon Sep 17 00:00:00 2001 From: Aman Arora Date: Mon, 2 Aug 2021 03:11:26 -0400 Subject: [PATCH 06/11] Minor edits from Feedback --- src/types/closure.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index d4519cbc2..61e6696db 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -331,7 +331,7 @@ If a closure captures a field of a composite types such as structs, tuples, and * Input: * Analyzing the closure C yields a mapping of `Place -> Mode` that are accessed * Access mode is `ref`, `ref uniq`, `ref mut`, or `by-value` (ordered least to max) - * For a `Place` that is used in two different acess modes within the same closure, the mode reported from closure analysis is the maximum access mode. + * For a `Place` that is used in two different access modes within the same closure, the mode reported from closure analysis is the maximum access mode. * Note: `ByValue` use of a `Copy` type is seen as a `ref` access mode. * Closure mode is `ref` or `move` * Output: @@ -397,6 +397,8 @@ If a closure captures a field of a composite types such as structs, tuples, and ### box-mut +This test shows how a `move` closure can sometimes capture values by mutable reference, if they are reached via a `&mut` reference. + ```rust struct Foo { x: i32 } @@ -419,10 +421,10 @@ fn box_mut() { ```ignore Closure mode = move -C = { +C_in = { (ref mut, (*(*bx)).x) } -C' = C +C_out = C_in ``` Output is the same: `C' = C` @@ -453,13 +455,16 @@ fn main() { ```ignore Closure mode = ref -C = { +C_in = { (ref mut, packed) } -C' = C +C_out = C_in ``` ### Optimization-Edge-Case + +This test shows an interesting edge case. Normally, when we see a borrow of something behind a shared reference (`&T`), we truncate to capture the entire reference, because that is more efficient (and we can always use that reference to reach all the data it refers to). However, in the case where we are dereferencing two shared references, we have to be sure to preserve the full path, since otherwise the resulting closure could have a shorter lifetime than is necessary. + ```edition2021 struct Int(i32); struct B<'a>(&'a i32); @@ -479,10 +484,10 @@ fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static { ```ignore Closure mode = ref -C = { +C_in = { (ref mut, *m.a) } -C' = C +C_out = C_in ``` # Edition 2018 and before From 367999718f3f1e6c7fe3b7790d2bfe166757917d Mon Sep 17 00:00:00 2001 From: Aman Arora Date: Mon, 2 Aug 2021 05:12:31 -0400 Subject: [PATCH 07/11] Add details on truncation and ref-uniq --- src/types/closure.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index 61e6696db..0645a9cb6 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -351,12 +351,12 @@ If a closure captures a field of a composite types such as structs, tuples, and * Helper functions: * `unsafe_check(Place, Mode) -> (Place, Mode)` * "Ensure unsafe accesses occur within the closure" - * If Place contains a deref of a raw pointer: - * Let Place1 = Place truncated just before the deref - * Return (Place1, Mode) - * If Mode is `ref *` and the place contains a field of a packed struct: - * Let Place1 = Place truncated just before the field - * Return (Place1, Mode) + * If Place contains a deref (at index `i`) of a raw pointer: + * Let `(Place1, Mode1) = truncate_place(Place, Mode, i)` + * Return (Place1, Mode1) + * If Mode is `ref *` and the place contains a field of a packed struct at index `i`: + * Let `(Place1, Mode1) = truncate_place(Place, Mode, i)` + * Return (Place1, Mode1) * Else * Return (Place, Mode) * `move_xform(Place, Mode) -> (Place, Mode)` (For move closures) @@ -367,16 +367,16 @@ If a closure captures a field of a composite types such as structs, tuples, and * Return (Place, Mode) * Else if Mode is `ref *` and the place contains a deref of an `&`: * Return (Place, Mode) - * Else if place contains a deref: - * Let Place1 = Place truncated just before the deref + * Else if place contains a deref at index `i`: + * Let `(Place1, _) = truncate_place(Place, Mode, i)` * Return (Place1, ByValue) * Else: * Return (Place, ByValue) * `ref_xform(Place, Mode) -> (Place, Mode)` (for ref closures) * "If taking ownership of data, only move data from enclosing stack frame." * Generate C' by mapping each (Mode, Place) in C - * If Mode is ByValue and place contains a deref: - * Let Place1 = Place truncated just before the deref + * If Mode is ByValue and place contains a deref at index `i`: + * Let `(Place1, _) = truncate_place(Place, Mode, i)` * Return (Place1, ByValue) * Else: * Return (Place, Mode) @@ -392,6 +392,13 @@ If a closure captures a field of a composite types such as structs, tuples, and and `Place.type_before_projection(l) = ty::Ref(.., Mutability::Not)` * Let Place1 = (Base, Projections[0..=l]) * Return (Place1, Ref) + * `truncate_place(Place, Mode, len) -> (Place, Mode)` + * "Truncate the place to length `len`, i.e. upto but not including index `len`" + * "If during truncation we drop Deref of a `&mut` and the place was being used by `ref mut`, the access to the truncated place must be unique" + * Let (Proj_before, Proj_after) = Place.split_before(len) + * If Mode == `ref mut` and there exists `Deref` in `Proj_after` at index `i` such that `Place.type_before_projection(len + i)` is `&mut T` + * Return (Place(Proj_before, ..InputPlace), `ref-uniq`) + * Else Return (Place(Proj_before, ..InputPlace), Mode) ## Key examples From bf995cc98b6720e3c1854358e1e4d719f9a37cd3 Mon Sep 17 00:00:00 2001 From: Aman Arora Date: Wed, 22 Sep 2021 00:54:10 -0700 Subject: [PATCH 08/11] Update closure algorithm per #88467 #88477 --- src/types/closure.md | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index 0645a9cb6..167a9486e 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -111,14 +111,14 @@ let c = || match x { ### Capturing references in move contexts -Moving fields out of references is not allowed. As a result, in the case of move closures, when values accessed through a shared references are moved into the closure body, the compiler, instead of moving the values out of the reference, would reborrow the data. +Moving fields out of references is not allowed. As a result, in the case of move closures, when values accessed through a shared references are moved into the closure body, the compiler will truncate right before a dereference. ```rust struct T(String, String); let mut t = T(String::from("foo"), String::from("bar")); let t = &mut t; -let c = move || t.0.truncate(0); // closure captures (&mut t.0) +let c = move || t.0.truncate(0); // closure captures `t` ``` ### Raw pointer dereference @@ -339,10 +339,11 @@ If a closure captures a field of a composite types such as structs, tuples, and * Cleanup and truncation * Generate C' by mapping each (Mode, Place) in C: * `(Place1, Mode1) = ref_opt(unsafe_check(Place, Mode))` - * if this is a ref closure: - * Add `ref_xform(Place1, Mode1)` to C' + * `(Place2, Mode2)` = if this is a ref closure: + * `ref_xform(Place1, Mode1)` * else: - * Add `move_xform(Place1, Mode1)` to C' + * `move_xform(Place1, Mode1)` + * Add `(Place3, Mode3) = truncate_move_through_drop(Place2, Mode2)` to C'. * Minimization * Until no rules apply: * For each two places (P1, M1), (P2, M2) where P1 is a prefix of P2: @@ -360,18 +361,12 @@ If a closure captures a field of a composite types such as structs, tuples, and * Else * Return (Place, Mode) * `move_xform(Place, Mode) -> (Place, Mode)` (For move closures) - * "Take ownership if data being accessed is owned by the variable used to access it (or if closure attempts to move data that it doesn't own)." - * "When taking ownership, only capture data found on the stack." - * "Otherwise, reborrow the reference." - * If Mode is `ref mut` and the place contains a deref of an `&mut`: - * Return (Place, Mode) - * Else if Mode is `ref *` and the place contains a deref of an `&`: - * Return (Place, Mode) - * Else if place contains a deref at index `i`: + * If place contains a deref at index `i`: * Let `(Place1, _) = truncate_place(Place, Mode, i)` * Return (Place1, ByValue) * Else: * Return (Place, ByValue) + * Note that initially we had considered an approach where "Take ownership if data being accessed is owned by the variable used to access it (or if closure attempts to move data that it doesn't own). That is when taking ownership only capture data that is found on the stack otherwise reborrow the reference.". This cause a bug around lifetimes, check [rust-lang/rust#88431](https://github.com/rust-lang/rust/issues/88431). * `ref_xform(Place, Mode) -> (Place, Mode)` (for ref closures) * "If taking ownership of data, only move data from enclosing stack frame." * Generate C' by mapping each (Mode, Place) in C @@ -392,6 +387,16 @@ If a closure captures a field of a composite types such as structs, tuples, and and `Place.type_before_projection(l) = ty::Ref(.., Mutability::Not)` * Let Place1 = (Base, Projections[0..=l]) * Return (Place1, Ref) + * `truncate_move_through_drop(Place1, Mode1) -> (Place, Mode)` + * Rust doesn't permit moving out of a type that implements drop + * In the case where we do a disjoint capture in a move closure, we might end up trying to move out of drop type + * We truncate move of not-Copy types + * If Mode1 != ByBalue + * return (Place1, Mode1) + * If there exists `i` such that `Place1.before_projection(i): Drop` and `Place1.ty()` doesn't impl `Copy` + * then return `truncate_place(Place1, Mode1, i)` + * Else return (Place1, Mode1) + * Check [rust-lang/rust#88476](https://github.com/rust-lang/rust/issues/88476) for examples. * `truncate_place(Place, Mode, len) -> (Place, Mode)` * "Truncate the place to length `len`, i.e. upto but not including index `len`" * "If during truncation we drop Deref of a `&mut` and the place was being used by `ref mut`, the access to the truncated place must be unique" @@ -411,11 +416,11 @@ struct Foo { x: i32 } fn box_mut() { let mut s = Foo { x: 0 } ; - + let px = &mut s; let bx = Box::new(px); - - + + let c = move || bx.x += 10; // Mutable reference to this place: // (*(*bx)).x From 7ce7a283d94ba8a0fcd0311fdd04afb894c5b18a Mon Sep 17 00:00:00 2001 From: Aman Arora Date: Fri, 19 Nov 2021 14:06:04 -0800 Subject: [PATCH 09/11] Update src/types/closure.md Co-authored-by: Josh Triplett --- src/types/closure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/closure.md b/src/types/closure.md index 167a9486e..a842b8db0 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -9,7 +9,7 @@ closure: ```rust #[derive(Debug)] -struct Point { x:i32, y:i32 } +struct Point { x: i32, y: i32 } struct Rectangle { left_top: Point, right_bottom: Point } fn f String> (g: F) { From 934b8c4cf09b2915199acc9be8fb583e06c4fa04 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 8 Jul 2024 12:18:06 -0700 Subject: [PATCH 10/11] Update the closure chapter for more clarity, correctness, and completeness. --- src/types/closure.md | 551 +++++++++++++++++++++++-------------------- 1 file changed, 293 insertions(+), 258 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index a842b8db0..09cca1e0d 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -2,10 +2,9 @@ r[type.closure] -A [closure expression] produces a closure value with a unique, anonymous type -that cannot be written out. A closure type is approximately equivalent to a -struct which contains the captured values. For instance, the following -closure: +A [closure expression] produces a closure value with a unique, anonymous type that cannot be written out. +A closure type is approximately equivalent to a struct which contains the captured values. +For instance, the following closure: ```rust #[derive(Debug)] @@ -26,13 +25,16 @@ let c = || { rect.right_bottom.x += 1; format!("{:?}", rect.left_top) }; -// Prints "Point { x: 2, y: 1 }". +f(c); // Prints "Point { x: 2, y: 1 }". ``` generates a closure type roughly like the following: ```rust,ignore +// Note: This is not exactly how it is translated, this is only for +// illustration. + struct Closure<'a> { left_top : &'a mut Point, right_bottom_x : &'a mut i32, @@ -40,9 +42,9 @@ struct Closure<'a> { impl<'a> FnOnce<()> for Closure<'a> { type Output = String; - fn call_once(self) -> String { + extern "rust-call" fn call_once(self, args: ()) -> String { self.left_top.x += 1; - self.right_bottom_x += 1; + *self.right_bottom_x += 1; format!("{:?}", self.left_top) } } @@ -52,35 +54,84 @@ so that the call to `f` works as if it were: ```rust,ignore -f(Closure{ left_top: rect.left_top, right_bottom_x: rect.left_top.x }); +// Note: This is not valid Rust due to the duplicate mutable borrows. +// This is only provided as an illustration. +f(Closure{ left_top: &mut rect.left_top, right_bottom_x: &mut rect.left_top.x }); ``` ## Capture modes r[type.closure.capture] -r[type.closure.capture.order] -The compiler prefers to capture a value by immutable borrow, -followed by unique immutable borrow (see below), by mutable borrow, and finally -by move. It will pick the first choice of these that is compatible with how the -captured value is used inside the closure body. The compiler does not take -surrounding code into account, such as the lifetimes of involved variables or fields, or -of the closure itself. +r[type.closure.capture.intro] +A *capture mode* determines how a [place expression] from the environment is borrowed or moved into the closure. +The capture modes are: + +1. Immutable borrow (`ImmBorrow`) --- The place expression is captured as a [shared reference]. +2. Unique immutable borrow (`UniqueImmBorrow`) --- This is similar to an immutable borrow, but must be unique as described [below](#unique-immutable-borrows-in-captures). +3. Mutable borrow (`MutBorrow`) --- The place expression is captured as a [mutable reference]. +4. Move (`ByValue`) --- The place expression is captured by [moving the value] into the closure. + +r[type.closure.capture.precedence] +Place expressions from the environment are captured from the first mode that is compatible with how the captured value is used inside the closure body. +The mode is not affected by the code surrounding the closure, such as the lifetimes of involved variables or fields, or of the closure itself. + +[moving the value]: ../expressions.md#moved-and-copied-types +[mutable reference]: pointer.md#mutable-references-mut +[place expression]: ../expressions.md#place-expressions-and-value-expressions +[shared reference]: pointer.md#references--and-mut + +### `Copy` values + +Values that implement [`Copy`] that are moved into the closure are captured with the `ImmBorrow` mode. + +```rust +let x = [0; 1024]; +let c = || { + let y = x; // x captured by ImmBorrow +}; +``` ## Capture Precision -The precise path that gets captured is typically the full path that is used in the closure, but there are cases where we will only capture a prefix of the path. +A *capture path* is a sequence starting with a variable from the environment followed by zero or more place projections that were applied to that variable. + +A *place projection* is a [field access], [tuple index], [dereference] (and automatic dereferences), or [array or slice index] expression applied to a variable. + +The closure borrows or moves the capture path, which may be truncated based on the rules described below. + +For example: + +```rust +struct SomeStruct { + f1: (i32, i32), +} +let s = SomeStruct { f1: (1, 2) }; + +let c = || { + let x = s.f1.1; // s.f1.1 captured by ImmBorrow +}; +c(); +``` +Here the capture path is the local variable `s`, followed by a field access `.f1`, and then a tuple index `.1`. +This closure captures an immutable borrow of `s.f1.1`. + +[field access]: ../expressions/field-expr.md +[tuple index]: ../expressions/tuple-expr.md#tuple-indexing-expressions +[dereference]: ../expressions/operator-expr.md#the-dereference-operator +[array or slice index]: ../expressions/array-expr.md#array-and-slice-indexing-expressions ### Shared prefix -In the case where a path and one of the ancestor’s of that path are both captured by a closure, the ancestor path is captured with the highest capture mode among the two captures,`CaptureMode = max(AncestorCaptureMode, DescendantCaptureMode)`, using the strict weak ordering +In the case where a capture path and one of the ancestor’s of that path are both captured by a closure, the ancestor path is captured with the highest capture mode among the two captures, `CaptureMode = max(AncestorCaptureMode, DescendantCaptureMode)`, using the strict weak ordering: -`ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue`. +`ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue` Note that this might need to be applied recursively. ```rust +// In this example, there are three different capture paths with a shared ancestor: # fn move_value(_: T){} let s = String::from("S"); let t = (s, String::from("T")); @@ -91,106 +142,272 @@ let c = || { u.1.truncate(0); // u.0 captured by MutBorrow move_value(u.0.0); // u.0.0 captured by ByValue }; +c(); +``` + +Overall this closure will capture `u` by `ByValue`. + +### Rightmost shared reference truncation + +The capture path is truncated at the rightmost dereference in the capture path if the dereference is applied to a shared reference. + +This truncation is allowed because fields that are read through a shared reference will always be read via a shared reference or a copy. +This helps reduce the size of the capture when the extra precision does not yield any benefit from a borrow checking perspective. + +The reason it is the *rightmost* dereference is to help avoid a shorter lifetime than is necessary. +Consider the following example: + +```rust +struct Int(i32); +struct B<'a>(&'a i32); + +struct MyStruct<'a> { + a: &'static Int, + b: B<'a>, +} + +fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static { + let c = || drop(&m.a.0); + c +} ``` -Overall the closure will capture `u` by `ByValue`. +If this were to capture `m`, then the closure would no longer outlive `'static`, since `m` is constrained to `'a`. Instead, it captures `(*(*m).a)` by `ImmBorrow`. + +### Wildcard pattern bindings -### Wild Card Patterns -Closures only capture data that needs to be read, which means the following closures will not capture `x` +Closures only capture data that needs to be read. +Binding a value with a [wildcard pattern] does not count as a read, and thus won't be captured. +For example, the following closures will not capture `x`: ```rust -let x = 10; +let x = String::from("hello"); let c = || { - let _ = x; + let _ = x; // x is not captured }; +c(); -let c = || match x { +let c = || match x { // x is not captured _ => println!("Hello World!") }; +c(); ``` +This also includes destructuring of tuples, structs, and enums. +Fields matched with the [_RestPattern_] or [_StructPatternEtCetera_] are also not considered as read, and thus those fields will not be captured. +The following illustrates some of these: + +```rust +let x = (String::from("a"), String::from("b")); +let c = || { + let (first, ..) = x; // captures `x.0` ByValue +}; +// The first tuple field has been moved into the closure. +// The second tuple field is still accessible. +println!("{:?}", x.1); +c(); +``` + +```rust +struct Example { + f1: String, + f2: String, +} + +let e = Example { + f1: String::from("first"), + f2: String::from("second"), +}; +let c = || { + let Example { f2, .. } = e; // captures `e.f2` ByValue +}; +// Field f2 cannot be accessed since it is moved into the closure. +// Field f1 is still accessible. +println!("{:?}", e.f1); +c(); +``` + +Partial captures of arrays and slices are not supported; the entire slice or array is always captured even if used with wildcard pattern matching, indexing, or sub-slicing. +For example: + +```rust,compile_fail,E0382 +#[derive(Debug)] +struct Example; +let x = [Example, Example]; + +let c = || { + let [first, _] = x; // captures all of `x` ByValue +}; +c(); +println!("{:?}", x[1]); // ERROR: borrow of moved value: `x` +``` + +Values that are matched with wildcards must still be initialized. + +```rust,compile_fail,E0381 +let x: i32; +let c = || { + let _ = x; // ERROR: used binding `x` isn't initialized +}; +``` + +[_RestPattern_]: ../patterns.md#rest-patterns +[_StructPatternEtCetera_]: ../patterns.md#struct-patterns +[wildcard pattern]: ../patterns.md#wildcard-pattern + ### Capturing references in move contexts -Moving fields out of references is not allowed. As a result, in the case of move closures, when values accessed through a shared references are moved into the closure body, the compiler will truncate right before a dereference. +Because it is not allowed to move fields out of a reference, `move` closures will only capture the prefix of a capture path that runs up to, but not including, the first dereference of a reference. +The reference itself will be moved into the closure. ```rust struct T(String, String); let mut t = T(String::from("foo"), String::from("bar")); -let t = &mut t; -let c = move || t.0.truncate(0); // closure captures `t` +let t_mut_ref = &mut t; +let mut c = move || { + t_mut_ref.0.push_str("123"); // captures `t_mut_ref` ByValue +}; +c(); ``` ### Raw pointer dereference -Because it is `unsafe` to dereference a raw pointer, closures will only capture the prefix of a path that runs up to, but not including, the first dereference of a raw pointer. -```rust, +Because it is `unsafe` to dereference a raw pointer, closures will only capture the prefix of a capture path that runs up to, but not including, the first dereference of a raw pointer. + +```rust struct T(String, String); let t = T(String::from("foo"), String::from("bar")); -let t = &t as *const T; +let t_ptr = &t as *const T; let c = || unsafe { - println!("{}", (*t).0); // closure captures t + println!("{}", (*t_ptr).0); // captures `t_ptr` by ImmBorrow +}; +c(); +``` + +### Union fields + +Because it is `unsafe` to access a union field, closures will only capture the prefix of a capture path that runs up to the union itself. + +```rust +union U { + a: (i32, i32), + b: bool, +} +let u = U { a: (123, 456) }; + +let c = || { + let x = unsafe { u.a.0 }; // captures `u` ByValue }; +c(); + +// This also includes writing to fields. +let mut u = U { a: (123, 456) }; + +let mut c = || { + u.b = true; // captures `u` with MutBorrow +}; +c(); ``` ### Reference into unaligned `struct`s -Because it is `unsafe` to hold references to unaligned fields in a structure, closures will only capture the prefix of the path that runs up to, but not including, the first field access into an unaligned structure. +Because it is [undefined behavior] to create references to unaligned fields in a structure, +closures will only capture the prefix of the capture path that runs up to, but not including, the first field access into a structure that uses [the `packed` representation]. +This includes all fields, even those that are aligned, to protect against compatibility concerns should any of the fields in the structure change in the future. ```rust #[repr(packed)] +struct T(i32, i32); + +let t = T(2, 5); +let c = || { + let a = t.0; // captures `t` with ImmBorrow +}; +// Copies out of `t` are ok. +let (a, b) = (t.0, t.1); +c(); +``` + +Similarly, taking the address of an unaligned field also captures the entire struct: + +```rust,compile_fail,E0505 +#[repr(packed)] struct T(String, String); -let t = T(String::from("foo"), String::from("bar")); -let c = || unsafe { - println!("{}", t.0); // closure captures t +let mut t = T(String::new(), String::new()); +let c = || { + let a = std::ptr::addr_of!(t.1); // captures `t` with ImmBorrow +}; +let a = t.0; // ERROR: cannot move out of `t.0` because it is borrowed +c(); +``` + +but the above works if it is not packed since it captures the field precisely: + +```rust +struct T(String, String); + +let mut t = T(String::new(), String::new()); +let c = || { + let a = std::ptr::addr_of!(t.1); // captures `t.1` with ImmBorrow }; +// The move here is allowed. +let a = t.0; +c(); ``` +[undefined behavior]: ../behavior-considered-undefined.md +[the `packed` representation]: ../type-layout.md#the-alignment-modifiers ### `Box` vs other `Deref` implementations The implementation of the [`Deref`] trait for [`Box`] is treated differently from other `Deref` implementations, as it is considered a special entity. -For example, let us look at examples involving `Rc` and `Box`. The `*rc` is desugared to a call to the trait method `deref` defined on `Rc`, but since `*box` is treated differently by the compiler, the compiler is able to do precise capture on contents of the `Box`. +For example, let us look at examples involving `Rc` and `Box`. The `*rc` is desugared to a call to the trait method `deref` defined on `Rc`, but since `*box` is treated differently, it is possible to do a precise capture of the contents of the `Box`. [`Box`]: ../special-types-and-traits.md#boxt [`Deref`]: ../special-types-and-traits.md#deref-and-derefmut -#### Non `move` closure +#### `Box` with non-`move` closure -In a non `move` closure, if the contents of the `Box` are not moved into the closure body, the contents of the `Box` are precisely captured. +In a non-`move` closure, if the contents of the `Box` are not moved into the closure body, the contents of the `Box` are precisely captured. ```rust -# use std::rc::Rc; +struct S(String); -struct S(i32); - -let b = Box::new(S(10)); +let b = Box::new(S(String::new())); let c_box = || { - println!("{}", (*b).0); // closure captures `(*b).0` + let x = &(*b).0; // captures `(*b).0` by ImmBorrow }; +c_box(); -let r = Rc::new(S(10)); +// Contrast `Box` with another type that implements Deref: +let r = std::rc::Rc::new(S(String::new())); let c_rc = || { - println!("{}", (*r).0); // closure caprures `r` + let x = &(*r).0; // captures `r` by ImmBorrow }; +c_rc(); ``` However, if the contents of the `Box` are moved into the closure, then the box is entirely captured. This is done so the amount of data that needs to be moved into the closure is minimized. ```rust -struct S(i32); +// This is the same as the example above except the closure +// moves the value instead of taking a reference to it. -let b = Box::new(S(10)); +struct S(String); + +let b = Box::new(S(String::new())); let c_box = || { - let x = (*b).0; // closure captures `b` + let x = (*b).0; // captures `b` with ByValue }; +c_box(); ``` -#### `move` closure +#### `Box` with move closure Similarly to moving contents of a `Box` in a non-`move` closure, reading the contents of a `Box` in a `move` closure will capture the `Box` entirely. @@ -198,41 +415,39 @@ Similarly to moving contents of a `Box` in a non-`move` closure, reading the con struct S(i32); let b = Box::new(S(10)); -let c_box = || { - println!("{}", (*b).0); // closure captures `b` +let c_box = move || { + let x = (*b).0; // captures `b` with ByValue }; ``` ## Unique immutable borrows in captures r[type.closure.unique-immutable] - -Captures can occur by a special kind of borrow called a _unique immutable -borrow_, which cannot be used anywhere else in the language and cannot be -written out explicitly. It occurs when modifying the referent of a mutable -reference, as in the following example: +Captures can occur by a special kind of borrow called a _unique immutable borrow_, +which cannot be used anywhere else in the language and cannot be written out explicitly. +It occurs when modifying the referent of a mutable reference, as in the following example: ```rust let mut b = false; let x = &mut b; -{ - let mut c = || { *x = true; }; - // The following line is an error: - // let y = &x; - c(); -} +let mut c = || { + // An ImmBorrow and a MutBorrow of `x`. + let a = &x; + *x = true; // `x` captured by UniqueImmBorrow +}; +// The following line is an error: +// let y = &x; +c(); +// However, the following is OK. let z = &x; ``` In this case, borrowing `x` mutably is not possible, because `x` is not `mut`. But at the same time, borrowing `x` immutably would make the assignment illegal, -because a `& &mut` reference might not be unique, so it cannot safely be used to -modify a value. So a unique immutable borrow is used: it borrows `x` immutably, -but like a mutable borrow, it must be unique. In the above example, uncommenting -the declaration of `y` will produce an error because it would violate the -uniqueness of the closure's borrow of `x`; the declaration of z is valid because -the closure's lifetime has expired at the end of the block, releasing the borrow. +because a `& &mut` reference might not be unique, so it cannot safely be used to modify a value. +So a unique immutable borrow is used: it borrows `x` immutably, but like a mutable borrow, it must be unique. +In the above example, uncommenting the declaration of `y` will produce an error because it would violate the uniqueness of the closure's borrow of `x`; the declaration of z is valid because the closure's lifetime has expired at the end of the block, releasing the borrow. ## Call traits and coercions @@ -325,188 +540,11 @@ If a closure captures a field of a composite types such as structs, tuples, and } // tuple.1 dropped here -----------------------------+ ``` +## Edition 2018 and before -## Overall Capture analysis algorithm - -* Input: - * Analyzing the closure C yields a mapping of `Place -> Mode` that are accessed - * Access mode is `ref`, `ref uniq`, `ref mut`, or `by-value` (ordered least to max) - * For a `Place` that is used in two different access modes within the same closure, the mode reported from closure analysis is the maximum access mode. - * Note: `ByValue` use of a `Copy` type is seen as a `ref` access mode. - * Closure mode is `ref` or `move` -* Output: - * Minimal `(Place, Mode)` pairs that are actually captured -* Cleanup and truncation - * Generate C' by mapping each (Mode, Place) in C: - * `(Place1, Mode1) = ref_opt(unsafe_check(Place, Mode))` - * `(Place2, Mode2)` = if this is a ref closure: - * `ref_xform(Place1, Mode1)` - * else: - * `move_xform(Place1, Mode1)` - * Add `(Place3, Mode3) = truncate_move_through_drop(Place2, Mode2)` to C'. -* Minimization - * Until no rules apply: - * For each two places (P1, M1), (P2, M2) where P1 is a prefix of P2: - * Remove both places from the set - * Add (P1, max(M1, M2)) into the set -* Helper functions: - * `unsafe_check(Place, Mode) -> (Place, Mode)` - * "Ensure unsafe accesses occur within the closure" - * If Place contains a deref (at index `i`) of a raw pointer: - * Let `(Place1, Mode1) = truncate_place(Place, Mode, i)` - * Return (Place1, Mode1) - * If Mode is `ref *` and the place contains a field of a packed struct at index `i`: - * Let `(Place1, Mode1) = truncate_place(Place, Mode, i)` - * Return (Place1, Mode1) - * Else - * Return (Place, Mode) - * `move_xform(Place, Mode) -> (Place, Mode)` (For move closures) - * If place contains a deref at index `i`: - * Let `(Place1, _) = truncate_place(Place, Mode, i)` - * Return (Place1, ByValue) - * Else: - * Return (Place, ByValue) - * Note that initially we had considered an approach where "Take ownership if data being accessed is owned by the variable used to access it (or if closure attempts to move data that it doesn't own). That is when taking ownership only capture data that is found on the stack otherwise reborrow the reference.". This cause a bug around lifetimes, check [rust-lang/rust#88431](https://github.com/rust-lang/rust/issues/88431). - * `ref_xform(Place, Mode) -> (Place, Mode)` (for ref closures) - * "If taking ownership of data, only move data from enclosing stack frame." - * Generate C' by mapping each (Mode, Place) in C - * If Mode is ByValue and place contains a deref at index `i`: - * Let `(Place1, _) = truncate_place(Place, Mode, i)` - * Return (Place1, ByValue) - * Else: - * Return (Place, Mode) - * `ref_opt(Place, Mode) -> (Place, Mode)` - * "Optimization: borrow the ref, not data owned by ref." - * Disjoint capture over immutable reference doesn't add too much value because the fields can either be borrowed immutably or copied. - * Edge case: Field that is accessed via the referece lives longer than the reference. - * Resolution: Only consider the last Deref - * If Place is (Base, Projections), where Projections is a list of size N. - * For all `i, 0 <= i < N`, Projections[i] != Deref - * Return (Place, Mode) - * If `l, 0 <= l < N` is the last/rightmost Deref Projection i.e. for any `i, l < i < N` Projection[i] != Deref, - and `Place.type_before_projection(l) = ty::Ref(.., Mutability::Not)` - * Let Place1 = (Base, Projections[0..=l]) - * Return (Place1, Ref) - * `truncate_move_through_drop(Place1, Mode1) -> (Place, Mode)` - * Rust doesn't permit moving out of a type that implements drop - * In the case where we do a disjoint capture in a move closure, we might end up trying to move out of drop type - * We truncate move of not-Copy types - * If Mode1 != ByBalue - * return (Place1, Mode1) - * If there exists `i` such that `Place1.before_projection(i): Drop` and `Place1.ty()` doesn't impl `Copy` - * then return `truncate_place(Place1, Mode1, i)` - * Else return (Place1, Mode1) - * Check [rust-lang/rust#88476](https://github.com/rust-lang/rust/issues/88476) for examples. - * `truncate_place(Place, Mode, len) -> (Place, Mode)` - * "Truncate the place to length `len`, i.e. upto but not including index `len`" - * "If during truncation we drop Deref of a `&mut` and the place was being used by `ref mut`, the access to the truncated place must be unique" - * Let (Proj_before, Proj_after) = Place.split_before(len) - * If Mode == `ref mut` and there exists `Deref` in `Proj_after` at index `i` such that `Place.type_before_projection(len + i)` is `&mut T` - * Return (Place(Proj_before, ..InputPlace), `ref-uniq`) - * Else Return (Place(Proj_before, ..InputPlace), Mode) - -## Key examples - -### box-mut - -This test shows how a `move` closure can sometimes capture values by mutable reference, if they are reached via a `&mut` reference. - -```rust -struct Foo { x: i32 } - -fn box_mut() { - let mut s = Foo { x: 0 } ; - - let px = &mut s; - let bx = Box::new(px); - - - let c = move || bx.x += 10; - // Mutable reference to this place: - // (*(*bx)).x - // ^ ^ - // | a Box - // a &mut -} -``` - - -```ignore -Closure mode = move -C_in = { - (ref mut, (*(*bx)).x) -} -C_out = C_in -``` - -Output is the same: `C' = C` - -### Packed-field-ref-and-move - -When you have a closure that both references a packed field (which is unsafe) and moves from it (which is safe) we capture the entire struct, rather than just moving the field. This is to aid in predictability, so that removing the move doesn't make the closure become unsafe: - -```rust -#[repr(packed)] -struct Packed { x: String } - -# fn use_ref(_: &T) {} -# fn move_value(_: T) {} - -fn main() { - let packed = Packed { x: String::new() }; - - let c = || { - use_ref(&packed.x); - move_value(packed.x); - }; +### Closure types difference - c(); -} -``` - - -```ignore -Closure mode = ref -C_in = { - (ref mut, packed) -} -C_out = C_in -``` - -### Optimization-Edge-Case - -This test shows an interesting edge case. Normally, when we see a borrow of something behind a shared reference (`&T`), we truncate to capture the entire reference, because that is more efficient (and we can always use that reference to reach all the data it refers to). However, in the case where we are dereferencing two shared references, we have to be sure to preserve the full path, since otherwise the resulting closure could have a shorter lifetime than is necessary. - -```edition2021 -struct Int(i32); -struct B<'a>(&'a i32); - -struct MyStruct<'a> { - a: &'static Int, - b: B<'a>, -} - -fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static { - let c = || drop(&m.a.0); - c -} - -``` - - -```ignore -Closure mode = ref -C_in = { - (ref mut, *m.a) -} -C_out = C_in -``` - -# Edition 2018 and before - -## Closure types difference - -In Edition 2018 and before, a closure would capture variables in its entirety. This means that for the example used in the [Closure types](#closure-types) section, the generated closure type would instead look something like this: +In Edition 2018 and before, closures always capture a variable in its entirety, without its precise capture path. This means that for the example used in the [Closure types](#closure-types) section, the generated closure type would instead look something like this: ```rust,ignore @@ -516,20 +554,22 @@ struct Closure<'a> { impl<'a> FnOnce<()> for Closure<'a> { type Output = String; - fn call_once(self) -> String { + extern "rust-call" fn call_once(self, args: ()) -> String { self.rect.left_top.x += 1; self.rect.right_bottom.x += 1; format!("{:?}", self.rect.left_top) } } ``` + and the call to `f` would work as follows: + ```rust,ignore f(Closure { rect: rect }); ``` -## Capture precision difference +### Capture precision difference r[type.closure.capture.composite] Composite types such as structs, tuples, and enums are always captured in its entirety, @@ -553,19 +593,14 @@ impl SetVec { } ``` -If, instead, the closure were to use `self.vec` directly, then it would attempt -to capture `self` by mutable reference. But since `self.set` is already -borrowed to iterate over, the code would not compile. +If, instead, the closure were to use `self.vec` directly, then it would attempt to capture `self` by mutable reference. But since `self.set` is already borrowed to iterate over, the code would not compile. r[type.closure.capture.move] -If the `move` keyword is used, then all captures are by move or, for `Copy` -types, by copy, regardless of whether a borrow would work. The `move` keyword is -usually used to allow the closure to outlive the captured values, such as if the -closure is being returned or used to spawn a new thread. +If the `move` keyword is used, then all captures are by move or, for `Copy` types, by copy, regardless of whether a borrow would work. The `move` keyword is usually used to allow the closure to outlive the captured values, such as if the closure is being returned or used to spawn a new thread. Regardless of if the data will be read by the closure, i.e. in case of wild card patterns, if a variable defined outside the closure is mentioned within the closure the variable will be captured in its entirety. -## Drop order difference +### Drop order difference As composite types are captured in their entirety, a closure which captures one of those composite types by value would drop the entire captured variable at the same time as the closure gets dropped. From c3a46908c66059831279224dc38b76ff228dc920 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 4 Nov 2024 10:47:37 -0800 Subject: [PATCH 11/11] Add rule identifiers --- src/types/closure.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index 09cca1e0d..d46d7f7ae 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -2,6 +2,7 @@ r[type.closure] +r[type.closure.intro] A [closure expression] produces a closure value with a unique, anonymous type that cannot be written out. A closure type is approximately equivalent to a struct which contains the captured values. For instance, the following closure: @@ -83,6 +84,7 @@ The mode is not affected by the code surrounding the closure, such as the lifeti ### `Copy` values +r[type.closure.capture.copy] Values that implement [`Copy`] that are moved into the closure are captured with the `ImmBorrow` mode. ```rust @@ -94,10 +96,13 @@ let c = || { ## Capture Precision +r[type.closure.capture.precision.capture-path] A *capture path* is a sequence starting with a variable from the environment followed by zero or more place projections that were applied to that variable. +r[type.closure.capture.precision.place-projection] A *place projection* is a [field access], [tuple index], [dereference] (and automatic dereferences), or [array or slice index] expression applied to a variable. +r[type.closure.capture.precision.intro] The closure borrows or moves the capture path, which may be truncated based on the rules described below. For example: @@ -124,6 +129,7 @@ This closure captures an immutable borrow of `s.f1.1`. ### Shared prefix +r[type.closure.capture.precision.shared-prefix] In the case where a capture path and one of the ancestor’s of that path are both captured by a closure, the ancestor path is captured with the highest capture mode among the two captures, `CaptureMode = max(AncestorCaptureMode, DescendantCaptureMode)`, using the strict weak ordering: `ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue` @@ -149,6 +155,7 @@ Overall this closure will capture `u` by `ByValue`. ### Rightmost shared reference truncation +r[type.closure.capture.precision.dereference-shared] The capture path is truncated at the rightmost dereference in the capture path if the dereference is applied to a shared reference. This truncation is allowed because fields that are read through a shared reference will always be read via a shared reference or a copy. @@ -176,6 +183,7 @@ If this were to capture `m`, then the closure would no longer outlive `'static`, ### Wildcard pattern bindings +r[type.closure.capture.precision.wildcard] Closures only capture data that needs to be read. Binding a value with a [wildcard pattern] does not count as a read, and thus won't be captured. For example, the following closures will not capture `x`: @@ -227,6 +235,7 @@ println!("{:?}", e.f1); c(); ``` +r[type.closure.capture.precision.wildcard.array-slice] Partial captures of arrays and slices are not supported; the entire slice or array is always captured even if used with wildcard pattern matching, indexing, or sub-slicing. For example: @@ -242,6 +251,7 @@ c(); println!("{:?}", x[1]); // ERROR: borrow of moved value: `x` ``` +r[type.closure.capture.precision.wildcard.initialized] Values that are matched with wildcards must still be initialized. ```rust,compile_fail,E0381 @@ -257,6 +267,7 @@ let c = || { ### Capturing references in move contexts +r[type.closure.capture.precision.move-dereference] Because it is not allowed to move fields out of a reference, `move` closures will only capture the prefix of a capture path that runs up to, but not including, the first dereference of a reference. The reference itself will be moved into the closure. @@ -273,6 +284,7 @@ c(); ### Raw pointer dereference +r[type.closure.capture.precision.raw-pointer-dereference] Because it is `unsafe` to dereference a raw pointer, closures will only capture the prefix of a capture path that runs up to, but not including, the first dereference of a raw pointer. ```rust @@ -289,6 +301,7 @@ c(); ### Union fields +r[type.closure.capture.precision.union] Because it is `unsafe` to access a union field, closures will only capture the prefix of a capture path that runs up to the union itself. ```rust @@ -314,6 +327,7 @@ c(); ### Reference into unaligned `struct`s +r[type.closure.capture.precision.unaligned] Because it is [undefined behavior] to create references to unaligned fields in a structure, closures will only capture the prefix of the capture path that runs up to, but not including, the first field access into a structure that uses [the `packed` representation]. This includes all fields, even those that are aligned, to protect against compatibility concerns should any of the fields in the structure change in the future. @@ -364,6 +378,7 @@ c(); ### `Box` vs other `Deref` implementations +r[type.closure.capture.precision.box-deref] The implementation of the [`Deref`] trait for [`Box`] is treated differently from other `Deref` implementations, as it is considered a special entity. For example, let us look at examples involving `Rc` and `Box`. The `*rc` is desugared to a call to the trait method `deref` defined on `Rc`, but since `*box` is treated differently, it is possible to do a precise capture of the contents of the `Box`. @@ -373,6 +388,7 @@ For example, let us look at examples involving `Rc` and `Box`. The `*rc` is desu #### `Box` with non-`move` closure +r[type.closure.capture.precision.box-non-move.not-moved] In a non-`move` closure, if the contents of the `Box` are not moved into the closure body, the contents of the `Box` are precisely captured. ```rust @@ -392,6 +408,7 @@ let c_rc = || { c_rc(); ``` +r[type.closure.capture.precision.box-non-move.moved] However, if the contents of the `Box` are moved into the closure, then the box is entirely captured. This is done so the amount of data that needs to be moved into the closure is minimized. ```rust @@ -409,6 +426,7 @@ c_box(); #### `Box` with move closure +r[type.closure.capture.precision.box-move.read] Similarly to moving contents of a `Box` in a non-`move` closure, reading the contents of a `Box` in a `move` closure will capture the `Box` entirely. ```rust @@ -525,6 +543,7 @@ Because captures are often by reference, the following general rules arise: ## Drop Order +r[type.closure.drop-order] If a closure captures a field of a composite types such as structs, tuples, and enums by value, the field's lifetime would now be tied to the closure. As a result, it is possible for disjoint fields of a composite types to be dropped at different times. ```rust @@ -544,6 +563,7 @@ If a closure captures a field of a composite types such as structs, tuples, and ### Closure types difference +r[type.closure.capture.precision.edition2018.entirety] In Edition 2018 and before, closures always capture a variable in its entirety, without its precise capture path. This means that for the example used in the [Closure types](#closure-types) section, the generated closure type would instead look something like this: @@ -571,7 +591,7 @@ f(Closure { rect: rect }); ### Capture precision difference -r[type.closure.capture.composite] +r[type.closure.capture.precision.edition2018.composite] Composite types such as structs, tuples, and enums are always captured in its entirety, not by individual fields. As a result, it may be necessary to borrow into a local variable in order to capture a single field: @@ -595,13 +615,15 @@ impl SetVec { If, instead, the closure were to use `self.vec` directly, then it would attempt to capture `self` by mutable reference. But since `self.set` is already borrowed to iterate over, the code would not compile. -r[type.closure.capture.move] +r[type.closure.capture.precision.edition2018.move] If the `move` keyword is used, then all captures are by move or, for `Copy` types, by copy, regardless of whether a borrow would work. The `move` keyword is usually used to allow the closure to outlive the captured values, such as if the closure is being returned or used to spawn a new thread. +r[type.closure.capture.precision.edition2018.wildcard] Regardless of if the data will be read by the closure, i.e. in case of wild card patterns, if a variable defined outside the closure is mentioned within the closure the variable will be captured in its entirety. ### Drop order difference +r[type.closure.capture.precision.edition2018.drop-order] As composite types are captured in their entirety, a closure which captures one of those composite types by value would drop the entire captured variable at the same time as the closure gets dropped. ```rust