Compare commits

...

18 Commits

Author SHA1 Message Date
Roxane 8d92a87400
Merge 44f6e1e20f into 51817951d0 2024-04-27 20:32:52 +01:00
Eric Huss 51817951d0
Merge pull request #1468 from petrochenkov/debmac
Add docs for `#[collapse_debuginfo]` attribute
2024-04-27 17:54:45 +00:00
Eric Huss 2d51a2aec4 Add an example of collapse_debuginfo 2024-04-27 10:53:08 -07:00
Eric Huss c495b9660f Link `collapse_debuginfo` in the index of built-in attributes. 2024-02-14 10:21:12 -08:00
Eric Huss 860fe4acc1 Use semantic line wrapping. 2024-02-14 10:18:40 -08:00
Eric Huss 4e9c91f0ec Place `rustc` behavior in a side note.
Generally the reference tries to stay focused on the language, and only
provide implementation notes as side-information.
2024-02-14 10:17:29 -08:00
Eric Huss 224b6c5306 Use em-dash separator 2024-02-14 10:16:40 -08:00
Eric Huss bb166095d1 Use standard template introducing an attribute. 2024-02-14 10:16:23 -08:00
Vadim Petrochenkov 0bf5d4e44c Add docs for `#[collapse_debuginfo]` attribute 2024-02-13 16:26:42 +03:00
Aman Arora 44f6e1e20f
Update src/types/closure.md
Co-authored-by: Josh Triplett <josh@joshtriplett.org>
2021-11-19 14:06:04 -08:00
Aman Arora 702a71f187 Update closure algorithm per #88467 #88477 2021-10-05 00:55:54 -07:00
Aman Arora fe03349f87 Add details on truncation and ref-uniq 2021-10-05 00:36:42 -07:00
Aman Arora 55c0829f2c Minor edits from Feedback 2021-10-05 00:36:42 -07:00
Roxane 7944512783 fix stray merge conflict 2021-10-05 00:36:42 -07:00
Aman Arora 52a9af968e Update copy type, optimization, make algorithm (place, mode) 2021-10-05 00:36:42 -07:00
Aman Arora fa56c013aa Copy capture analyis alogrithm from hackmd 2021-10-05 00:36:42 -07:00
Roxane 124ece696a Address comments 2021-10-05 00:36:42 -07:00
Roxane 7c776c83fe Update closure types documentation so it includes information about RFC2229 2021-10-05 00:36:42 -07:00
3 changed files with 462 additions and 44 deletions

View File

@ -275,6 +275,7 @@ The following is an index of all built-in attributes.
added in future.
- Debugger
- [`debugger_visualizer`] — Embeds a file that specifies debugger output for a type.
- [`collapse_debuginfo`] — Controls how macro invocations are encoded in debuginfo.
[Doc comments]: comments.md#doc-comments
[ECMA-334]: https://www.ecma-international.org/publications-and-standards/standards/ecma-334/
@ -293,6 +294,7 @@ The following is an index of all built-in attributes.
[`cfg_attr`]: conditional-compilation.md#the-cfg_attr-attribute
[`cfg`]: conditional-compilation.md#the-cfg-attribute
[`cold`]: attributes/codegen.md#the-cold-attribute
[`collapse_debuginfo`]: attributes/debugger.md#the-collapse_debuginfo-attribute
[`crate_name`]: crates-and-source-files.md#the-crate_name-attribute
[`crate_type`]: linkage.md
[`debugger_visualizer`]: attributes/debugger.md#the-debugger_visualizer-attribute

View File

@ -139,3 +139,32 @@ When the crate's debug executable is passed into GDB[^rust-gdb], `print bob` wil
[Natvis documentation]: https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects
[pretty printing documentation]: https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html
[_MetaListNameValueStr_]: ../attributes.md#meta-item-attribute-syntax
## The `collapse_debuginfo` attribute
The *`collapse_debuginfo` [attribute]* controls whether code locations from a macro definition are collapsed into a single location associated with the macro's call site,
when generating debuginfo for code calling this macro.
The attribute uses the [_MetaListIdents_] syntax to specify its inputs, and can only be applied to macro definitions.
Accepted options:
- `#[collapse_debuginfo(yes)]` — code locations in debuginfo are collapsed.
- `#[collapse_debuginfo(no)]` — code locations in debuginfo are not collapsed.
- `#[collapse_debuginfo(external)]` — code locations in debuginfo are collapsed only if the macro comes from a different crate.
The `external` behavior is the default for macros that don't have this attribute, unless they are built-in macros.
For built-in macros the default is `yes`.
> **Note**: `rustc` has a `-C collapse-macro-debuginfo` CLI option to override both the default collapsing behavior and `#[collapse_debuginfo]` attributes.
```rust
#[collapse_debuginfo(yes)]
macro_rules! example {
() => {
println!("hello!");
};
}
```
[attribute]: ../attributes.md
[_MetaListIdents_]: ../attributes.md#meta-item-attribute-syntax

View File

@ -2,38 +2,46 @@
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<F : FnOnce() -> String> (g: F) {
println!("{}", g());
}
let mut s = String::from("foo");
let t = String::from("bar");
let mut rect = Rectangle {
left_top: Point { x: 1, y: 1 },
right_bottom: Point { x: 0, y: 0 }
};
f(|| {
s += &t;
s
});
// Prints "foobar".
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:
<!-- ignore: simplified, requires unboxed_closures, fn_traits -->
<!-- ignore: simplified -->
```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)
}
}
```
@ -42,48 +50,153 @@ so that the call to `f` works as if it were:
<!-- ignore: continuation of above -->
```rust,ignore
f(Closure{s: s, t: &t});
f(Closure{ left_top: rect.left_top, right_bottom_x: rect.left_top.x });
```
## Capture modes
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
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.
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
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 ancestors 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
# use std::collections::HashSet;
#
struct SetVec {
set: HashSet<u32>,
vec: Vec<u32>
}
# fn move_value<T>(_: T){}
let s = String::from("S");
let t = (s, String::from("T"));
let mut u = (t, String::from("U"));
impl SetVec {
fn populate(&mut self) {
let vec = &mut self.vec;
self.set.iter().for_each(|&n| {
vec.push(n);
})
}
}
let c = || {
println!("{:?}", u); // u captured by ImmBorrow
u.1.truncate(0); // u.0 captured by MutBorrow
move_value(u.0.0); // u.0.0 captured by ByValue
};
```
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.
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
let x = 10;
let c = || {
let _ = x;
};
let c = || match x {
_ => println!("Hello World!")
};
```
### 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.
```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`
```
### 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,
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
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)]
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 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.
```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
@ -113,6 +226,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
Closure types all implement [`FnOnce`], indicating that they can be called once
@ -156,12 +270,13 @@ following traits if allowed to do so by the types of the captures it stores:
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
@ -178,3 +293,275 @@ 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 -----------------------------+
```
## 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: Omit error about unterminated string literal when representing c_prime -->
```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>(_: &T) {}
# fn move_value<T>(_: T) {}
fn main() {
let packed = Packed { x: String::new() };
let c = || {
use_ref(&packed.x);
move_value(packed.x);
};
c();
}
```
<!-- ignore: Omit error about unterminated string literal when representing c_prime -->
```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: Omit error about unterminated string literal when reprenting c_prime -->
```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:
<!-- ignore: simplified -->
```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:
<!-- ignore: continuation of above -->
```rust,ignore
f(Closure { rect: rect });
```
## Capture precision difference
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
# use std::collections::HashSet;
#
struct SetVec {
set: HashSet<u32>,
vec: Vec<u32>
}
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.
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 ------------+
}
```