Skip to content

Commit d238a8c

Browse files
committed
Update closure types documentation so it includes information about RFC2229
1 parent ab60513 commit d238a8c

File tree

2 files changed

+251
-45
lines changed

2 files changed

+251
-45
lines changed

.DS_Store

8 KB
Binary file not shown.

src/types/closure.md

+251-45
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,46 @@
22

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

88
```rust
9+
#[derive(Debug)]
10+
struct Point { x:i32, y:i32 }
11+
struct Rectangle { left_top: Point, right_bottom: Point }
12+
913
fn f<F : FnOnce() -> String> (g: F) {
1014
println!("{}", g());
1115
}
1216

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

2330
generates a closure type roughly like the following:
2431

25-
<!-- ignore: simplified, requires unboxed_closures, fn_traits -->
32+
<!-- ignore: simplified -->
2633
```rust,ignore
2734
struct Closure<'a> {
28-
s : String,
29-
t : &'a String,
35+
left_top : &'a mut Point,
36+
right_bottom_x : &'a mut i32,
3037
}
3138
3239
impl<'a> FnOnce<()> for Closure<'a> {
3340
type Output = String;
3441
fn call_once(self) -> String {
35-
self.s += &*self.t;
36-
self.s
42+
self.left_top.x += 1;
43+
self.right_bottom_x += 1;
44+
format!("{:?}", self.left_top)
3745
}
3846
}
3947
```
@@ -42,48 +50,149 @@ so that the call to `f` works as if it were:
4250

4351
<!-- ignore: continuation of above -->
4452
```rust,ignore
45-
f(Closure{s: s, t: &t});
53+
f(Closure{ left_top: rect.left_top, right_bottom_x: rect.left_top.x });
4654
```
4755

4856
## Capture modes
4957

50-
The compiler prefers to capture a closed-over variable by immutable borrow,
58+
The compiler prefers to capture a value by immutable borrow,
5159
followed by unique immutable borrow (see below), by mutable borrow, and finally
52-
by move. It will pick the first choice of these that allows the closure to
60+
by value (move or copy). It will pick the first choice of these that allows the closure to
5361
compile. The choice is made only with regards to the contents of the closure
5462
expression; the compiler does not take into account surrounding code, such as
55-
the lifetimes of involved variables.
63+
the lifetimes of involved variables or fields.
5664

57-
If the `move` keyword is used, then all captures are by move or, for `Copy`
58-
types, by copy, regardless of whether a borrow would work. The `move` keyword is
59-
usually used to allow the closure to outlive the captured values, such as if the
60-
closure is being returned or used to spawn a new thread.
65+
## Capture Precision
66+
67+
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.
68+
69+
70+
### Shared prefix
6171

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

6695
```rust
67-
# use std::collections::HashSet;
68-
#
69-
struct SetVec {
70-
set: HashSet<u32>,
71-
vec: Vec<u32>
72-
}
96+
let x = 10;
97+
let c = || {
98+
let _ = x;
99+
};
100+
101+
let c = || match x {
102+
_ => println!("Hello World!")
103+
};
104+
```
73105

74-
impl SetVec {
75-
fn populate(&mut self) {
76-
let vec = &mut self.vec;
77-
self.set.iter().for_each(|&n| {
78-
vec.push(n);
79-
})
80-
}
81-
}
106+
### Capturing references in move contexts
107+
108+
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.
109+
110+
```rust
111+
struct T(String, String);
112+
113+
let mut t = T(String::from("foo"), String::from("bar"));
114+
let t = &mut t;
115+
let c = move || t.0.truncate(0); // closure captures (&mut t.0)
82116
```
83117

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

88197
## Unique immutable borrows in captures
89198

@@ -113,6 +222,7 @@ the declaration of `y` will produce an error because it would violate the
113222
uniqueness of the closure's borrow of `x`; the declaration of z is valid because
114223
the closure's lifetime has expired at the end of the block, releasing the borrow.
115224

225+
116226
## Call traits and coercions
117227

118228
Closure types all implement [`FnOnce`], indicating that they can be called once
@@ -156,12 +266,13 @@ following traits if allowed to do so by the types of the captures it stores:
156266

157267
The rules for [`Send`] and [`Sync`] match those for normal struct types, while
158268
[`Clone`] and [`Copy`] behave as if [derived]. For [`Clone`], the order of
159-
cloning of the captured variables is left unspecified.
269+
cloning of the captured values is left unspecified.
270+
160271

161272
Because captures are often by reference, the following general rules arise:
162273

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

0 commit comments

Comments
 (0)