From ab722efbd466104bd70faab5107da38863dc72ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Fri, 30 May 2025 23:09:53 +0200 Subject: [PATCH 1/2] Document how closure capturing interacts with discriminant reads This is the behavior after the bugfixes in rustc PR 138961. --- src/types/closure.md | 70 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index bcb82e98f..dc3927f32 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -98,7 +98,10 @@ Async closures always capture all input arguments, regardless of whether or not ## 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. +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, as well as +any [further projections performed by matching against patterns][pattern-wildcards]. + +[pattern-wildcards]: type.closure.capture.precision.wildcard 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. @@ -202,7 +205,7 @@ let c = || match x { // x is not captured c(); ``` -This also includes destructuring of tuples, structs, and enums. +This also includes destructuring of tuples, structs, and single-variant 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: @@ -264,6 +267,69 @@ let c = || { [wildcard pattern]: ../patterns.md#wildcard-pattern +r[type.closure.capture.precision.discriminants] +### Capturing for discriminant reads + +If pattern matching requires inspecting a discriminant, the relevant place will get captured by `ImmBorrow`. + +```rust +enum Example { + A(i32), + B(i32), +} + +let mut x = (Example::A(21), 37); + +let c = || match x { // captures `x.0` by ImmBorrow + (Example::A(_), _) => println!("variant A"), + (Example::B(_), _) => println!("variant B"), +}; +x.1 += 1; // x.1 can still be modified +c(); +``` + +r[type.closure.capture.precision.discriminants.single-variant] +Matching against the only variant of an enum does not constitute a discriminant read. + +```rust +enum Example { + A(i32), +} + +let mut x = Example::A(42); +let c = || { + let Example::A(_) = x; // does not capture `x` +}; +x = Example::A(57); // x can be modified while the closure is live +c(); +``` + +r[type.closure.capture.precision.discriminants.non-exhaustive] +If [the `#[non_exhaustive]` attribute][non_exhaustive] is applied to an enum +defined in an external crate, it is considered to have multiple variants, +even if only one variant is actually present. + +[non_exhaustive]: attributes.type-system.non_exhaustive + +r[type.closure.capture.precision.discriminants.uninhabited-variant] +Even if all other variants are uninhabited, the discriminant read still occurs. + +```rust,compile_fail,E0506 +enum Void {} + +enum Example { + A(i32), + B(Void), +} + +let mut x = Example::A(42); +let c = || { + let Example::A(_) = x; // captures `x` by ImmBorrow +}; +x = Example::A(57); // ERROR: cannot assign to `x` because it is borrowed +c(); +``` + r[type.closure.capture.precision.move-dereference] ### Capturing references in move contexts From 6ed91821eebdb4b65771ad8768726cdaadf2bd6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Fri, 30 May 2025 23:51:14 +0200 Subject: [PATCH 2/2] Document how range and slice patterns can constitute discriminant reads --- src/types/closure.md | 51 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/types/closure.md b/src/types/closure.md index dc3927f32..061756f24 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -300,7 +300,7 @@ let mut x = Example::A(42); let c = || { let Example::A(_) = x; // does not capture `x` }; -x = Example::A(57); // x can be modified while the closure is live +x = Example::A(57); // `x` can be modified while the closure is live c(); ``` @@ -330,6 +330,55 @@ x = Example::A(57); // ERROR: cannot assign to `x` because it is borrowed c(); ``` +r[type.closure.capture.precision.discriminants.range-patterns] +Matching against a [range pattern][patterns.range] constitutes a discriminant read, even if +the range matches all possible values. + +```rust,compile_fail,E0506 +let mut x = 7_u8; +let c = || { + let 0..=u8::MAX = x; // captures `x` by ImmBorrow +}; +x += 1; // ERROR: cannot assign to `x` because it is borrowed +c(); +``` + +r[type.closure.capture.precision.discriminants.slice-patterns] +Matching against a [slice pattern][patterns.slice] constitutes a discriminant read if +the slice pattern needs to inspect the length of the scrutinee. + +```rust,compile_fail,E0506 +let mut x: &mut [i32] = &mut [1, 2, 3]; +let c = || match x { // captures `*x` by ImmBorrow + [_, _, _] => println!("three elements"), + _ => println!("something else"), +}; +x[0] += 1; // ERROR: cannot assign to `x[_]` because it is borrowed +c(); +``` + +Thus, matching against an array doesn't constitute a discriminant read, as the length is fixed. + +```rust +let mut x: [i32; 3] = [1, 2, 3]; +let c = || match x { // does not capture `x` + [_, _, _] => println!("three elements, obviously"), +}; +x[0] += 1; // `x` can be modified while the closure is live +c(); +``` + +Likewise, a slice pattern that matches slices of all possible lengths does not constitute a discriminant read. + +```rust +let mut x: &mut [i32] = &mut [1, 2, 3]; +let c = || match x { // does not capture `x` + [..] => println!("always matches"), +}; +x[0] += 1; // `x` can be modified while the closure is live +c(); +``` + r[type.closure.capture.precision.move-dereference] ### Capturing references in move contexts