Skip to content

Commit 11f84ce

Browse files
authored
Merge pull request #1837 from meithecatte/discriminant-reads
Document how closure capturing interacts with discriminant reads
2 parents b1a00d6 + 010f9a8 commit 11f84ce

File tree

1 file changed

+222
-44
lines changed

1 file changed

+222
-44
lines changed

src/types/closure.md

Lines changed: 222 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,13 @@ Async closures always capture all input arguments, regardless of whether or not
9898
## Capture precision
9999

100100
r[type.closure.capture.precision.capture-path]
101-
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.
101+
A *capture path* is a sequence starting with a variable from the environment followed by zero or more place projections from that variable.
102102

103103
r[type.closure.capture.precision.place-projection]
104-
A *place projection* is a [field access], [tuple index], [dereference] (and automatic dereferences), or [array or slice index] expression applied to a variable.
104+
A *place projection* is a [field access], [tuple index], [dereference] (and automatic dereferences), [array or slice index] expression, or [pattern destructuring] applied to a variable.
105+
106+
> [!NOTE]
107+
> In `rustc`, pattern destructuring desugars into a series of dereferences and field or element accesses.
105108
106109
r[type.closure.capture.precision.intro]
107110
The closure borrows or moves the capture path, which may be truncated based on the rules described below.
@@ -124,14 +127,15 @@ Here the capture path is the local variable `s`, followed by a field access `.f1
124127
This closure captures an immutable borrow of `s.f1.1`.
125128

126129
[field access]: ../expressions/field-expr.md
130+
[pattern destructuring]: patterns.destructure
127131
[tuple index]: ../expressions/tuple-expr.md#tuple-indexing-expressions
128132
[dereference]: ../expressions/operator-expr.md#the-dereference-operator
129133
[array or slice index]: ../expressions/array-expr.md#array-and-slice-indexing-expressions
130134

131135
r[type.closure.capture.precision.shared-prefix]
132136
### Shared prefix
133137

134-
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:
138+
In the case where a capture 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:
135139

136140
`ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue`
137141

@@ -185,85 +189,259 @@ If this were to capture `m`, then the closure would no longer outlive `'static`,
185189
r[type.closure.capture.precision.wildcard]
186190
### Wildcard pattern bindings
187191

188-
Closures only capture data that needs to be read.
189-
Binding a value with a [wildcard pattern] does not count as a read, and thus won't be captured.
190-
For example, the following closures will not capture `x`:
192+
r[type.closure.capture.precision.wildcard.reads]
193+
Closures only capture data that needs to be read. Binding a value with a [wildcard pattern] does not read the value, so the place is not captured.
191194

192-
```rust
193-
let x = String::from("hello");
195+
```rust,no_run
196+
struct S; // A non-`Copy` type.
197+
let x = S;
194198
let c = || {
195-
let _ = x; // x is not captured
199+
let _ = x; // Does not capture `x`.
196200
};
201+
let c = || match x {
202+
_ => (), // Does not capture `x`.
203+
};
204+
x; // OK: `x` can be moved here.
197205
c();
206+
```
207+
208+
r[type.closure.capture.precision.wildcard.destructuring]
209+
Destructuring tuples, structs, and single-variant enums does not, by itself, cause a read or the place to be captured.
198210

199-
let c = || match x { // x is not captured
200-
_ => println!("Hello World!")
211+
> [!NOTE]
212+
> Enums marked with [`#[non_exhaustive]`][attributes.type-system.non_exhaustive] from other crates are always treated as having multiple variants. See *[type.closure.capture.precision.discriminants.non_exhaustive]*.
213+
214+
```rust,no_run
215+
struct S; // A non-`Copy` type.
216+
217+
// Destructuring tuples does not cause a read or capture.
218+
let x = (S,);
219+
let c = || {
220+
let (..) = x; // Does not capture `x`.
201221
};
222+
x; // OK: `x` can be moved here.
202223
c();
203-
```
204224
205-
This also includes destructuring of tuples, structs, and enums.
206-
Fields matched with the [RestPattern] or [StructPatternEtCetera] are also not considered as read, and thus those fields will not be captured.
207-
The following illustrates some of these:
225+
// Destructuring unit structs does not cause a read or capture.
226+
let x = S;
227+
let c = || {
228+
let S = x; // Does not capture `x`.
229+
};
230+
x; // OK: `x` can be moved here.
231+
c();
208232
209-
```rust
210-
let x = (String::from("a"), String::from("b"));
233+
// Destructuring structs does not cause a read or capture.
234+
struct W<T>(T);
235+
let x = W(S);
236+
let c = || {
237+
let W(..) = x; // Does not capture `x`.
238+
};
239+
x; // OK: `x` can be moved here.
240+
c();
241+
242+
// Destructuring single-variant enums does not cause a read
243+
// or capture.
244+
enum E<T> { V(T) }
245+
let x = E::V(S);
211246
let c = || {
212-
let (first, ..) = x; // captures `x.0` ByValue
247+
let E::V(..) = x; // Does not capture `x`.
213248
};
214-
// The first tuple field has been moved into the closure.
215-
// The second tuple field is still accessible.
216-
println!("{:?}", x.1);
249+
x; // OK: `x` can be moved here.
217250
c();
218251
```
219252

220-
```rust
221-
struct Example {
222-
f1: String,
223-
f2: String,
224-
}
253+
r[type.closure.capture.precision.wildcard.fields]
254+
Fields matched against [RestPattern] (`..`) or [StructPatternEtCetera] (also `..`) are not read, and those fields are not captured.
225255

226-
let e = Example {
227-
f1: String::from("first"),
228-
f2: String::from("second"),
229-
};
256+
```rust,no_run
257+
struct S; // A non-`Copy` type.
258+
let x = (S, S);
230259
let c = || {
231-
let Example { f2, .. } = e; // captures `e.f2` ByValue
260+
let (x0, ..) = x; // Captures `x.0` by `ByValue`.
232261
};
233-
// Field f2 cannot be accessed since it is moved into the closure.
234-
// Field f1 is still accessible.
235-
println!("{:?}", e.f1);
262+
// Only the first tuple field was captured by the closure.
263+
x.1; // OK: `x.1` can be moved here.
236264
c();
237265
```
238266

239267
r[type.closure.capture.precision.wildcard.array-slice]
240268
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.
241-
For example:
242269

243270
```rust,compile_fail,E0382
244-
#[derive(Debug)]
245-
struct Example;
246-
let x = [Example, Example];
247-
271+
struct S; // A non-`Copy` type.
272+
let mut x = [S, S];
248273
let c = || {
249-
let [first, _] = x; // captures all of `x` ByValue
274+
let [x0, _] = x; // Captures all of `x` by `ByValue`.
250275
};
251-
c();
252-
println!("{:?}", x[1]); // ERROR: borrow of moved value: `x`
276+
let _ = &mut x[1]; // ERROR: Borrow of moved value.
253277
```
254278

255279
r[type.closure.capture.precision.wildcard.initialized]
256280
Values that are matched with wildcards must still be initialized.
257281

258282
```rust,compile_fail,E0381
259-
let x: i32;
283+
let x: u8;
260284
let c = || {
261-
let _ = x; // ERROR: used binding `x` isn't initialized
285+
let _ = x; // ERROR: Binding `x` isn't initialized.
262286
};
263287
```
264288

265289
[wildcard pattern]: ../patterns.md#wildcard-pattern
266290

291+
r[type.closure.capture.precision.discriminants]
292+
### Capturing for discriminant reads
293+
294+
r[type.closure.capture.precision.discriminants.reads]
295+
If pattern matching reads a discriminant, the place containing that discriminant is captured by `ImmBorrow`.
296+
297+
r[type.closure.capture.precision.discriminants.multiple-variant]
298+
Matching against a variant of an enum that has more than one variant reads the discriminant, capturing the place by `ImmBorrow`.
299+
300+
```rust,compile_fail,E0502
301+
struct S; // A non-`Copy` type.
302+
let mut x = (Some(S), S);
303+
let c = || match x {
304+
(None, _) => (),
305+
// ^^^^
306+
// This pattern requires reading the discriminant, which
307+
// causes `x.0` to be captured by `ImmBorrow`.
308+
_ => (),
309+
};
310+
let _ = &mut x.0; // ERROR: Cannot borrow `x.0` as mutable.
311+
// ^^^
312+
// The closure is still live, so `x.0` is still immutably
313+
// borrowed here.
314+
c();
315+
```
316+
317+
```rust,no_run
318+
# struct S; // A non-`Copy` type.
319+
# let x = (Some(S), S);
320+
let c = || match x { // Captures `x.0` by `ImmBorrow`.
321+
(None, _) => (),
322+
_ => (),
323+
};
324+
// Though `x.0` is captured due to the discriminant read,
325+
// `x.1` is not captured.
326+
x.1; // OK: `x.1` can be moved here.
327+
c();
328+
```
329+
330+
r[type.closure.capture.precision.discriminants.single-variant]
331+
Matching against the only variant of a single-variant enum does not read the discriminant and does not capture the place.
332+
333+
```rust,no_run
334+
enum E<T> { V(T) } // A single-variant enum.
335+
let x = E::V(());
336+
let c = || {
337+
let E::V(_) = x; // Does not capture `x`.
338+
};
339+
x; // OK: `x` can be moved here.
340+
c();
341+
```
342+
343+
r[type.closure.capture.precision.discriminants.non_exhaustive]
344+
If [`#[non_exhaustive]`][attributes.type-system.non_exhaustive] is applied to an enum defined in an external crate, the enum is treated as having multiple variants for the purpose of deciding whether a read occurs, even if it actually has only one variant.
345+
346+
r[type.closure.capture.precision.discriminants.uninhabited-variants]
347+
Even if all variants but the one being matched against are uninhabited, making the pattern [irrefutable][patterns.refutable], the discriminant is still read if it otherwise would be.
348+
349+
```rust,compile_fail,E0502
350+
enum Empty {}
351+
let mut x = Ok::<_, Empty>(42);
352+
let c = || {
353+
let Ok(_) = x; // Captures `x` by `ImmBorrow`.
354+
};
355+
let _ = &mut x; // ERROR: Cannot borrow `x` as mutable.
356+
c();
357+
```
358+
359+
360+
r[type.closure.capture.precision.range-patterns]
361+
### Capturing and range patterns
362+
363+
r[type.closure.capture.precision.range-patterns.reads]
364+
Matching against a [range pattern][patterns.range] reads the place being matched, even if the range includes all possible values of the type, and captures the place by `ImmBorrow`.
365+
366+
```rust,compile_fail,E0502
367+
let mut x = 0u8;
368+
let c = || {
369+
let 0..=u8::MAX = x; // Captures `x` by `ImmBorrow`.
370+
};
371+
let _ = &mut x; // ERROR: Cannot borrow `x` as mutable.
372+
c();
373+
```
374+
375+
r[type.closure.capture.precision.slice-patterns]
376+
### Capturing and slice patterns
377+
378+
r[type.closure.capture.precision.slice-patterns.slices]
379+
Matching a slice against a [slice pattern][patterns.slice] other than one with only a single [rest pattern][patterns.rest] (i.e. `[..]`) is treated as a read of the length from the slice and captures the slice by `ImmBorrow`.
380+
381+
```rust,compile_fail,E0502
382+
let x: &mut [u8] = &mut [];
383+
let c = || match x { // Captures `*x` by `ImmBorrow`.
384+
&mut [] => (),
385+
// ^^
386+
// This matches a slice of exactly zero elements. To know whether the
387+
// scrutinee matches, the length must be read, causing the slice to
388+
// be captured.
389+
_ => (),
390+
};
391+
let _ = &mut *x; // ERROR: Cannot borrow `*x` as mutable.
392+
c();
393+
```
394+
395+
```rust,no_run
396+
let x: &mut [u8] = &mut [];
397+
let c = || match x { // Does not capture `*x`.
398+
[..] => (),
399+
// ^^ Rest pattern.
400+
};
401+
let _ = &mut *x; // OK: `*x` can be borrow here.
402+
c();
403+
```
404+
405+
> [!NOTE]
406+
> Perhaps surprisingly, even though the length is contained in the (wide) *pointer* to the slice, it is the place of the *pointee* (the slice) that is treated as read and is captured.
407+
>
408+
> ```rust,no_run
409+
> fn f<'l: 's, 's>(x: &'s mut &'l [u8]) -> impl Fn() + 'l {
410+
> // The closure outlives `'l` because it captures `**x`. If
411+
> // instead it captured `*x`, it would not live long enough
412+
> // to satisfy the `impl Fn() + 'l` bound.
413+
> || match *x { // Captures `**x` by `ImmBorrow`.
414+
> &[] => (),
415+
> _ => (),
416+
> }
417+
> }
418+
> ```
419+
>
420+
> In this way, the behavior is consistent with dereferencing to the slice in the scrutinee.
421+
>
422+
> ```rust,no_run
423+
> fn f<'l: 's, 's>(x: &'s mut &'l [u8]) -> impl Fn() + 'l {
424+
> || match **x { // Captures `**x` by `ImmBorrow`.
425+
> [] => (),
426+
> _ => (),
427+
> }
428+
> }
429+
> ```
430+
>
431+
> For details, see [Rust PR #138961](https://github.com/rust-lang/rust/pull/138961).
432+
433+
r[type.closure.capture.precision.slice-patterns.arrays]
434+
As the length of an array is fixed by its type, matching an array against a slice pattern does not by itself capture the place.
435+
436+
```rust,no_run
437+
let x: [u8; 1] = [0];
438+
let c = || match x { // Does not capture `x`.
439+
[_] => (), // Length is fixed.
440+
};
441+
x; // OK: `x` can be moved here.
442+
c();
443+
```
444+
267445
r[type.closure.capture.precision.move-dereference]
268446
### Capturing references in move contexts
269447

0 commit comments

Comments
 (0)