Skip to content

Commit 6d68a7f

Browse files
roxeloehuss
authored andcommitted
Update closure types documentation so it includes information about RFC2229
1 parent da0f6da commit 6d68a7f

File tree

1 file changed

+256
-50
lines changed

1 file changed

+256
-50
lines changed

src/types/closure.md

Lines changed: 256 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,46 @@ r[type.closure]
44

55
A [closure expression] produces a closure value with a unique, anonymous type
66
that cannot be written out. A closure type is approximately equivalent to a
7-
struct which contains the captured variables. For instance, the following
7+
struct which contains the captured values. For instance, the following
88
closure:
99

1010
```rust
11+
#[derive(Debug)]
12+
struct Point { x:i32, y:i32 }
13+
struct Rectangle { left_top: Point, right_bottom: Point }
14+
1115
fn f<F : FnOnce() -> String> (g: F) {
1216
println!("{}", g());
1317
}
1418

15-
let mut s = String::from("foo");
16-
let t = String::from("bar");
17-
18-
f(|| {
19-
s += &t;
20-
s
21-
});
22-
// Prints "foobar".
19+
let mut rect = Rectangle {
20+
left_top: Point { x: 1, y: 1 },
21+
right_bottom: Point { x: 0, y: 0 }
22+
};
23+
24+
let c = || {
25+
rect.left_top.x += 1;
26+
rect.right_bottom.x += 1;
27+
format!("{:?}", rect.left_top)
28+
};
29+
// Prints "Point { x: 2, y: 1 }".
2330
```
2431

2532
generates a closure type roughly like the following:
2633

27-
<!-- ignore: simplified, requires unboxed_closures, fn_traits -->
34+
<!-- ignore: simplified -->
2835
```rust,ignore
2936
struct Closure<'a> {
30-
s : String,
31-
t : &'a String,
37+
left_top : &'a mut Point,
38+
right_bottom_x : &'a mut i32,
3239
}
3340
3441
impl<'a> FnOnce<()> for Closure<'a> {
3542
type Output = String;
3643
fn call_once(self) -> String {
37-
self.s += &*self.t;
38-
self.s
44+
self.left_top.x += 1;
45+
self.right_bottom_x += 1;
46+
format!("{:?}", self.left_top)
3947
}
4048
}
4149
```
@@ -44,54 +52,153 @@ so that the call to `f` works as if it were:
4452

4553
<!-- ignore: continuation of above -->
4654
```rust,ignore
47-
f(Closure{s: s, t: &t});
55+
f(Closure{ left_top: rect.left_top, right_bottom_x: rect.left_top.x });
4856
```
4957

5058
## Capture modes
5159

5260
r[type.closure.capture]
5361

5462
r[type.closure.capture.order]
55-
The compiler prefers to capture a closed-over variable by immutable borrow,
63+
The compiler prefers to capture a value by immutable borrow,
5664
followed by unique immutable borrow (see below), by mutable borrow, and finally
57-
by move. It will pick the first choice of these that is compatible with how the
58-
captured variable is used inside the closure body. The compiler does not take
59-
surrounding code into account, such as the lifetimes of involved variables, or
60-
of the closure itself.
65+
by move. It will pick the first choice of these that allows the closure to
66+
compile. The choice is made only with regards to the contents of the closure
67+
expression; the compiler does not take into account surrounding code, such as
68+
the lifetimes of involved variables or fields.
69+
>>>>>>> 881f305... Update closure types documentation so it includes information about RFC2229
6170
62-
r[type.closure.capture.move]
63-
If the `move` keyword is used, then all captures are by move or, for `Copy`
64-
types, by copy, regardless of whether a borrow would work. The `move` keyword is
65-
usually used to allow the closure to outlive the captured values, such as if the
66-
closure is being returned or used to spawn a new thread.
71+
## Capture Precision
6772

68-
r[type.closure.capture.composite]
69-
Composite types such as structs, tuples, and enums are always captured entirely,
70-
not by individual fields. It may be necessary to borrow into a local variable in
71-
order to capture a single field:
72-
<!-- editor note, not true in Edition 2021 -->
73+
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.
74+
75+
76+
### Shared prefix
77+
78+
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
79+
80+
`ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue`.
81+
82+
Note that this might need to be applied recursively.
83+
84+
```rust=
85+
let s = String::new("S");
86+
let t = (s, String::new("T"));
87+
let mut u = (t, String::new("U"));
88+
89+
let c = || {
90+
println!("{:?}", u); // u captured by ImmBorrow
91+
u.0.truncate(0); // u.0 captured by MutBorrow
92+
move_value(u.0.0); // u.0.0 captured by ByValue
93+
};
94+
```
95+
96+
Overall the closure will capture `u` by `ByValue`.
97+
98+
### Wild Card Patterns
99+
Closures only capture data that needs to be read, which means the following closures will not capture `x`
73100

74101
```rust
75-
# use std::collections::HashSet;
76-
#
77-
struct SetVec {
78-
set: HashSet<u32>,
79-
vec: Vec<u32>
80-
}
102+
let x = 10;
103+
let c = || {
104+
let _ = x;
105+
};
106+
107+
let c = || match x {
108+
_ => println!("Hello World!")
109+
};
110+
```
81111

82-
impl SetVec {
83-
fn populate(&mut self) {
84-
let vec = &mut self.vec;
85-
self.set.iter().for_each(|&n| {
86-
vec.push(n);
87-
})
88-
}
89-
}
112+
### Capturing references in move contexts
113+
114+
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.
115+
116+
```rust
117+
struct T(String, String);
118+
119+
let mut t = T(String::from("foo"), String::from("bar"));
120+
let t = &mut t;
121+
let c = move || t.0.truncate(0); // closure captures (&mut t.0)
90122
```
91123

92-
If, instead, the closure were to use `self.vec` directly, then it would attempt
93-
to capture `self` by mutable reference. But since `self.set` is already
94-
borrowed to iterate over, the code would not compile.
124+
### Raw pointer dereference
125+
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.
126+
127+
```rust,
128+
struct T(String, String);
129+
130+
let t = T(String::from("foo"), String::from("bar"));
131+
let t = &t as *const T;
132+
133+
let c = || unsafe {
134+
println!("{}", (*t).0); // closure captures t
135+
};
136+
```
137+
138+
### Reference into unaligned `struct`s
139+
140+
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.
141+
142+
```rust
143+
#[repr(packed)]
144+
struct T(String, String);
145+
146+
let t = T(String::from("foo"), String::from("bar"));
147+
let c = || unsafe {
148+
println!("{}", t.0); // closure captures t
149+
};
150+
```
151+
152+
153+
### `Box` vs other `Deref` implementations
154+
155+
The compiler treats the implementation of the Deref trait for `Box` differently, as it is considered a special entity.
156+
157+
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`.
158+
159+
#### Non `move` closure
160+
161+
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.
162+
163+
```rust
164+
# use std::rc::Rc;
165+
166+
struct S(i32);
167+
168+
let b = Box::new(S(10));
169+
let c_box = || {
170+
println!("{}", (*b).0); // closure captures `(*b).0`
171+
};
172+
173+
let r = Rc::new(S(10));
174+
let c_rc = || {
175+
println!("{}", (*r).0); // closure caprures `r`
176+
};
177+
```
178+
179+
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.
180+
181+
```rust
182+
struct S(i32);
183+
184+
let b = Box::new(S(10));
185+
let c_box = || {
186+
let x = (*b).0; // closure captures `b`
187+
};
188+
```
189+
190+
#### `move` closure
191+
192+
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.
193+
194+
```rust
195+
struct S(i32);
196+
197+
let b = Box::new(S(10));
198+
let c_box = || {
199+
println!("{}", (*b).0); // closure captures `b`
200+
};
201+
```
95202

96203
## Unique immutable borrows in captures
97204

@@ -123,6 +230,7 @@ the declaration of `y` will produce an error because it would violate the
123230
uniqueness of the closure's borrow of `x`; the declaration of z is valid because
124231
the closure's lifetime has expired at the end of the block, releasing the borrow.
125232

233+
126234
## Call traits and coercions
127235

128236
r[type.closure.call]
@@ -176,12 +284,13 @@ following traits if allowed to do so by the types of the captures it stores:
176284
r[type.closure.traits.behavior]
177285
The rules for [`Send`] and [`Sync`] match those for normal struct types, while
178286
[`Clone`] and [`Copy`] behave as if [derived]. For [`Clone`], the order of
179-
cloning of the captured variables is left unspecified.
287+
cloning of the captured values is left unspecified.
288+
180289

181290
Because captures are often by reference, the following general rules arise:
182291

183-
* A closure is [`Sync`] if all captured variables are [`Sync`].
184-
* A closure is [`Send`] if all variables captured by non-unique immutable
292+
* A closure is [`Sync`] if all captured values are [`Sync`].
293+
* A closure is [`Send`] if all values captured by non-unique immutable
185294
reference are [`Sync`], and all values captured by unique immutable or mutable
186295
reference, copy, or move are [`Send`].
187296
* 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:
195304
[`Sync`]: ../special-types-and-traits.md#sync
196305
[closure expression]: ../expressions/closure-expr.md
197306
[derived]: ../attributes/derive.md
307+
308+
## Drop Order
309+
310+
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.
311+
312+
```rust
313+
{
314+
let tuple =
315+
(String::from("foo"), String::from("bar")); // --+
316+
{ // |
317+
let c = || { // ----------------------------+ |
318+
// tuple.0 is captured into the closure | |
319+
drop(tuple.0); // | |
320+
}; // | |
321+
} // 'c' and 'tuple.0' dropped here ------------+ |
322+
} // tuple.1 dropped here -----------------------------+
323+
```
324+
325+
# Edition 2018 and before
326+
327+
## Closure types difference
328+
329+
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:
330+
331+
<!-- ignore: simplified -->
332+
```rust,ignore
333+
struct Closure<'a> {
334+
rect : &'a mut Rectangle,
335+
}
336+
337+
impl<'a> FnOnce<()> for Closure<'a> {
338+
type Output = String;
339+
fn call_once(self) -> String {
340+
self.rect.left_top.x += 1;
341+
self.rect.right_bottom.x += 1;
342+
format!("{:?}", self.rect.left_top)
343+
}
344+
}
345+
```
346+
and the call to `f` would work as follows:
347+
<!-- ignore: continuation of above -->
348+
```rust,ignore
349+
f(Closure { rect: rect });
350+
```
351+
352+
## Capture precision difference
353+
354+
r[type.closure.capture.composite]
355+
Composite types such as structs, tuples, and enums are always captured in its intirety,
356+
not by individual fields. As a result, it may be necessary to borrow into a local variable in order to capture a single field:
357+
358+
```rust
359+
# use std::collections::HashSet;
360+
#
361+
struct SetVec {
362+
set: HashSet<u32>,
363+
vec: Vec<u32>
364+
}
365+
366+
impl SetVec {
367+
fn populate(&mut self) {
368+
let vec = &mut self.vec;
369+
self.set.iter().for_each(|&n| {
370+
vec.push(n);
371+
})
372+
}
373+
}
374+
```
375+
376+
If, instead, the closure were to use `self.vec` directly, then it would attempt
377+
to capture `self` by mutable reference. But since `self.set` is already
378+
borrowed to iterate over, the code would not compile.
379+
380+
r[type.closure.capture.move]
381+
If the `move` keyword is used, then all captures are by move or, for `Copy`
382+
types, by copy, regardless of whether a borrow would work. The `move` keyword is
383+
usually used to allow the closure to outlive the captured values, such as if the
384+
closure is being returned or used to spawn a new thread.
385+
386+
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.
387+
388+
## Drop order difference
389+
390+
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.
391+
392+
```rust
393+
{
394+
let tuple =
395+
(String::from("foo"), String::from("bar"));
396+
{
397+
let c = || { // --------------------------+
398+
// tuple is captured into the closure |
399+
drop(tuple.0); // |
400+
}; // |
401+
} // 'c' and 'tuple' dropped here ------------+
402+
}
403+
```

0 commit comments

Comments
 (0)