From ab722efbd466104bd70faab5107da38863dc72ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= <maya@compilercrim.es>
Date: Fri, 30 May 2025 23:09:53 +0200
Subject: [PATCH 1/4] 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?= <maya@compilercrim.es>
Date: Fri, 30 May 2025 23:51:14 +0200
Subject: [PATCH 2/4] 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
 

From dff4a644a176a87ba396a918a0d63bf584128cf1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= <maya@compilercrim.es>
Date: Fri, 11 Jul 2025 18:22:44 +0200
Subject: [PATCH 3/4] Don't call things "discriminant reads" just because they
 behave like ones

---
 src/types/closure.md | 21 ++++++++-------------
 1 file changed, 8 insertions(+), 13 deletions(-)

diff --git a/src/types/closure.md b/src/types/closure.md
index 061756f24..0c9598dd3 100644
--- a/src/types/closure.md
+++ b/src/types/closure.md
@@ -98,8 +98,7 @@ 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, as well as
-any [further projections performed by matching against patterns][pattern-wildcards].
+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
 
@@ -305,9 +304,7 @@ 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.
+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
 
@@ -331,8 +328,7 @@ 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.
+Matching against a [range pattern][patterns.range] performs a read of the place being matched, causing the closure to borrow it by `ImmBorrow`. This is the case even if the range matches all possible values.
 
 ```rust,compile_fail,E0506
 let mut x = 7_u8;
@@ -344,11 +340,10 @@ 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.
+Matching against a [slice pattern][patterns.slice] performs a read if the slice pattern needs to inspect the length of the scrutinee. The read will cause the closure to borrow the relevant place by `ImmBorrow`.
 
 ```rust,compile_fail,E0506
-let mut x: &mut [i32] = &mut [1, 2, 3];
+let x: &mut [i32] = &mut [1, 2, 3];
 let c = || match x { // captures `*x` by ImmBorrow
     [_, _, _] => println!("three elements"),
     _ => println!("something else"),
@@ -357,7 +352,7 @@ 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.
+As such, matching against an array doesn't itself cause any borrows, as the lengthh is fixed and doesn't need to be read.
 
 ```rust
 let mut x: [i32; 3] = [1, 2, 3];
@@ -368,10 +363,10 @@ 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.
+Likewise, a slice pattern that matches slices of all possible lengths does not constitute a read.
 
 ```rust
-let mut x: &mut [i32] = &mut [1, 2, 3];
+let x: &mut [i32] = &mut [1, 2, 3];
 let c = || match x { // does not capture `x`
     [..] => println!("always matches"),
 };

From 3f847d577d0c0eb049b9b8db3902d524318f96cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= <maya@compilercrim.es>
Date: Wed, 23 Jul 2025 17:46:20 +0200
Subject: [PATCH 4/4] Clarify what gets read by slice patterns

---
 src/types/closure.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/types/closure.md b/src/types/closure.md
index 0c9598dd3..e4264a522 100644
--- a/src/types/closure.md
+++ b/src/types/closure.md
@@ -340,7 +340,7 @@ c();
 ```
 
 r[type.closure.capture.precision.discriminants.slice-patterns]
-Matching against a [slice pattern][patterns.slice] performs a read if the slice pattern needs to inspect the length of the scrutinee. The read will cause the closure to borrow the relevant place by `ImmBorrow`.
+Matching against a [slice pattern][patterns.slice] that needs to inspect the length of the scrutinee performs a read of the pointer value in order to fetch the length. The read will cause the closure to borrow the relevant place by `ImmBorrow`.
 
 ```rust,compile_fail,E0506
 let x: &mut [i32] = &mut [1, 2, 3];
@@ -352,7 +352,7 @@ x[0] += 1; // ERROR: cannot assign to `x[_]` because it is borrowed
 c();
 ```
 
-As such, matching against an array doesn't itself cause any borrows, as the lengthh is fixed and doesn't need to be read.
+As such, matching against an array doesn't itself cause any borrows, as the lengthh is fixed and the pattern doesn't need to inspect it.
 
 ```rust
 let mut x: [i32; 3] = [1, 2, 3];