Skip to content

Document how closure capturing interacts with discriminant reads #1837

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions src/types/closure.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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;
Expand All @@ -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`.
Copy link
Member

@Nadrieril Nadrieril Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe say "performs a read of the pointer value" to avoid confusion about what is being read? Should we even say that this reads only the pointer metadata (not sure that's what we're doing today even)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, how about this new revision?


```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"),
Expand All @@ -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];
Expand All @@ -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"),
};
Expand Down
Loading