-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Match ergonomics 2024 #3627
base: master
Are you sure you want to change the base?
Match ergonomics 2024 #3627
Changes from 1 commit
cbf6f22
f42a677
6081324
9e1bb8e
70650ac
8e4b83c
1d1d1f4
e222363
9b44733
0e1306b
e649309
a75166c
ab8be9a
5754a9c
19e0c7c
099b71f
07b35d8
3ff0267
b078485
c2bdf38
04501bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,337 @@ | ||
- Feature Name: `ref_pat_eat_one_layer_2024` | ||
- Start Date: 2024-05-06 | ||
- RFC PR: [rust-lang/rfcs#3627](https://github.com/rust-lang/rfcs/pull/3627) | ||
- Rust Issue: [rust-lang/rust#123076](https://github.com/rust-lang/rust/issues/123076) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Various changes to the match ergonomics rules: | ||
|
||
- On edition ≥ 2024, `&` and `&mut` patterns only remove a single layer of | ||
references. | ||
- On edition ≥ 2024, `mut` on an identifier pattern does not force its binding | ||
mode to by-value. | ||
- On edition ≥ 2024, `&` patterns can match against `&mut` references. | ||
- On all editions, the binding mode can no longer ever be implicitly set to | ||
`ref mut` behind an `&` pattern. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Match ergonomics have been a great success overall, but there are some surprising | ||
interactions that regularly confuse users. | ||
|
||
- `mut` resets the binding mode to by-value, which users do not expect; the | ||
mutability of the binding seems like a separate concern from its type | ||
(<https://github.com/rust-lang/rust/issues/105647>, | ||
<https://github.com/rust-lang/rust/issues/112545>) | ||
- `&` and `&mut` patterns must correspond with a reference in the same position | ||
in the scrutinee, even if there is an inherited reference present. Therefore, | ||
users have no general mechanism to "cancel out" an inherited reference | ||
(<https://users.rust-lang.org/t/reference-of-tuple-and-tuple-of-reference/91713/6>, | ||
<https://users.rust-lang.org/t/cannot-deconstruct-reference-inside-match-on-reference-why/92147>, | ||
<https://github.com/rust-lang/rust/issues/50008>, | ||
<https://github.com/rust-lang/rust/issues/64586>) | ||
- When an `&` or `&mut` pattern is used in a location where there is also an | ||
inherited reference present, both are stripped; adding a single `&` to the | ||
pattern can remove two `&`s from the type of the binding. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
Match ergonomics works a little differently in edition 2024 and above. | ||
|
||
## Matching against inherited references | ||
|
||
In all editions, when you match against an `&` or `&mut` reference with the type | ||
of its referent, you get an "inherited reference": the binding mode of | ||
"downstream" bindings is set to `ref` or `ref mut`. | ||
|
||
```rust | ||
// Unchanged from old editions: | ||
// `x` "inherits" the `&` from the scrutinee type. | ||
let [x] = &[42]; | ||
let _: &u8 = x; | ||
``` | ||
|
||
In edition 2024 and above, an `&` or `&mut` pattern can match against this | ||
inherited reference, consuming it. A pattern that does this has no other effect. | ||
|
||
```rust | ||
// New in edition 2023: | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// `&` pattern consumes inherited `&` reference. | ||
let [&x] = &[42]; | ||
let _: u8 = x; | ||
``` | ||
|
||
## `&` matches against `&mut` | ||
|
||
In edition 2024 and above, `&` patterns can match against `&mut` references | ||
(including "inherited" references). | ||
|
||
```rust | ||
let &foo = &mut 42; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
let &foo = &mut 42;
foo = 53; // error[E0384]: cannot assign twice to immutable variable `foo` There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. (It would be immutable with |
||
let _: u8 = foo; | ||
``` | ||
|
||
## `mut` no longer strips the inherited reference | ||
|
||
In older editions, `mut` on a binding "stripped" the inherited reference: | ||
|
||
```rust | ||
// Old editions | ||
let (x, mut y) = &(true, false); | ||
let _: (&bool, bool) = (x, y); | ||
``` | ||
|
||
This no longer happens on edition ≥ 2024. | ||
|
||
|
||
```rust | ||
// Edition ≥ 2024 | ||
let (x, mut y) = &(true, false); | ||
let _: (&bool, &bool) = (x, y); | ||
``` | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
This explanation assumes familiarity with the current match ergonomics rules, | ||
including the "default binding mode" terminology. Refer to [RFC 2005](./2005-match-ergonomics.md#detailed-design). | ||
|
||
## Edition 2024: `&` patterns can match against `&mut` references | ||
|
||
`&` patterns can match against `&mut` references. | ||
|
||
```rust | ||
let &foo = &mut 42; | ||
let _: u8 = foo; | ||
``` | ||
|
||
However, the `ref mut` binding mode cannot be used behind such patterns. | ||
|
||
```rust | ||
let &ref mut foo = &mut 42; | ||
// ^~ERROR: replace `&` with `&mut` | ||
let _: &mut u8 = foo; | ||
``` | ||
|
||
## Edition 2024: `&` and `&mut` can match against inherited references | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like it! With this, is there even a reason to keep supporting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course, without it we can't get desugar match ergonomics that get mutable references into places: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, okay. Sounds worthy of a clippy lint for if it's not combined with |
||
|
||
When the default binding mode is `ref` or `ref mut`, `&` and `&mut` patterns can | ||
reset it. `&` patterns will reset either `ref` or `ref mut` binding modes to | ||
by-value, while `&mut` can only reset `ref mut`. An `&` or `&mut` pattern that | ||
resets the binding mode in this way has no other effect. | ||
|
||
```rust | ||
let [&x] = &[3u8]; | ||
let _: u8 = x; | ||
|
||
let [&mut x] = &mut [3u8]; | ||
let _: u8 = x; | ||
|
||
let [&x] = &mut [3u8]; | ||
let _: u8 = x; | ||
|
||
//let [&mut x] = &[3u8]; // ERROR | ||
``` | ||
|
||
`&` patterns are otherwise unchanged from older editions. | ||
|
||
```rust | ||
let &a = &3; | ||
let _: u8 = a; | ||
|
||
//let &b = 17; // ERROR | ||
``` | ||
|
||
If the default binding mode is `ref`, then `&mut` patterns are forbidden. If it | ||
is by-value, then they have the same effect as on older editions. | ||
|
||
```rust | ||
//let [&mut x] = &[&mut 42]; // ERROR | ||
|
||
// Unchanged from old editions | ||
|
||
let &mut x = &mut 3; | ||
let _: u8 = x; | ||
|
||
let &mut x = &mut &mut 3; | ||
let _: &mut u8 = x; | ||
|
||
let &mut x = &mut &&mut 3; | ||
let _: &&mut u8 = x; | ||
|
||
//let &mut x = &&mut 3; // ERROR | ||
``` | ||
|
||
## Edition 2024: `mut` does not reset binding mode to by-value | ||
|
||
In the new edition, `mut` no longer resets the binding mode to by-value. | ||
Therefore, it is possible to have a mutable by-reference binding. (An explicit | ||
syntax for this is left to a future RFC.) | ||
|
||
```rust | ||
let &[mut a] = &[42]; | ||
a = &47; | ||
``` | ||
|
||
## All editions: the default binding mode is never set to `ref mut` behind an `&` pattern or reference | ||
|
||
The binding mode is set to `ref` instead in such cases. (On older editions, this | ||
allows strictly more code to compile.) | ||
|
||
```rust | ||
// All editions | ||
|
||
let &[[a]]; = &[&mut [42]]; | ||
let _: &u8 = a; | ||
|
||
let &[[a]]; = &mut [&mut [42]]; | ||
let _: &u8 = a; | ||
``` | ||
|
||
```rust | ||
// Edition ≥ 2024 | ||
|
||
let &[[&a]]; = &[&mut [42]]; | ||
let _: u8 = a; | ||
|
||
//let &[[&mut a]]; = &[&mut [42]]; // ERROR | ||
``` | ||
|
||
# Migration | ||
[migration]: #migration | ||
|
||
This proposal, if adopted, would allow the same pattern to have different | ||
meanings on different editions: | ||
|
||
```rust | ||
let [&a] = &[&0u8]; // `a` is `u8` on edition ≤ 2021, but `&u8` on edition ≥ 2024 | ||
let [mut a] = &[0u8]; // `a` is `u8` on edition ≤ 2021, but `&u8` on edition ≥ 2024 | ||
``` | ||
|
||
Instances of such incompatibilities appear to be common, but far from unknown | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(20 cases in `rustc`, for example). The migration lint for the feature entirely | ||
desugars the match ergonomics of the affected pattern. This is necessary to | ||
produce code that works on all editions, but it means that adopting the new | ||
rules could require editing the affected patterns twice: once to desugar the | ||
match ergonomics before adopting the new edition, and a second time to restore | ||
match ergonomics after adoption of the new edition. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
This is a silent change in behavior, which is considered undesirable even | ||
over an edition. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
## Desirable property | ||
[desirable-property]: #desirable-property | ||
|
||
The proposed rules for new editions uphold the following property: | ||
|
||
> For any two nested patterns `$pat0` and `$pat1`, such that `$pat1` uses match | ||
> ergonomics only (no explicit `ref`/`ref mut`), and valid pattern match | ||
> `let $pat0($pat1(binding)) = scrut`, either: | ||
> | ||
> - `let $pat0(temp) = scrut; let $pat1(binding) = temp;` compiles, with the | ||
> same meaning as the original composed pattern match; or | ||
> - `let $pat0(temp) = scrut; let $pat1(binding) = temp;` does not compile, but | ||
> `let $pat0(ref temp) = scrut; let &$pat1(binding) = temp;` compiles, with the | ||
> same meaning as the original composed pattern match. | ||
|
||
In other words, the new match ergonomics rules are compositional. | ||
|
||
## `&` patterns matching against `&mut` | ||
|
||
There are several motivations for allowing this: | ||
|
||
- It makes refactoring less painful. Sometimes, one is not certain whether an | ||
unfinished API will end up returning a shared or a mutable reference. But as | ||
long as the reference returned by said API is not actually used to perform | ||
mutation, it often doesn't matter either way, as `&mut` implicitly reborrows | ||
as `&` in many situations. Pattern matching is currently one of the most | ||
prominent exceptions to this, and match ergonomics magnifies the pain because | ||
a reference in one part of the pattern can affect the binding mode in a | ||
different, faraway location[^nrmba]. If patterns can be written to always use | ||
`&` unless mutation is required, then the amount of editing necessary to | ||
perform various refactors is lessened. | ||
- It's intuitive. `&mut` is strictly more powerful than `&`. It's conceptually a | ||
subtype, and even if not implemented that way[^sub], coercions mean it often | ||
feels like one in practice. | ||
|
||
```rust | ||
let a: &u8 = &mut 42; | ||
``` | ||
|
||
[^nrmba]: This is even more true in light of the new rule that prevents the | ||
default binding mode from being set to `ref mut` behind `&`. | ||
|
||
[^sub]: Making `&mut` a subtype of `&` in actual implementation would require | ||
adding significant complexity to the variance rules, but I do believe it to be | ||
possible. | ||
|
||
## `mut` not resetting the binding mode | ||
|
||
Admittedly, there is not much use for mutable by-reference bindings. This is | ||
true even outside of pattern matching; `let mut ident: &T = ...` is not commonly | ||
seen (though not entirely unknown either). The motivation for making this change | ||
anyway is that the current behavior is unintuitive and surprising for users. | ||
|
||
## Versus "eat-two-layers" | ||
|
||
An alternative proposal would be to allow `&` and `&mut` patterns to reset the | ||
binding mode when not matching against a reference in the same position in the | ||
scrutinee, but to not otherwise change their behavior. This would have the | ||
advantage of not requiring an edition change. However, it would remain confusing | ||
for users. Notably, the [property from earlier](#desirable-property) would | ||
continue to not be satisfied. | ||
|
||
In addition, this approach would lead to tricky questions around when | ||
mutabilities should be considered compatible. | ||
|
||
(This alternative is currently implemented under a separate feature gate.) | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
- How much churn will be necessary to adapt code for the new edition? There are | ||
0 instances of affected patterns in the standard library, and 20 in the | ||
compiler, but that is all the data we have at the moment. | ||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
- An explicit syntax for mutable by-reference bindings should be chosen at some | ||
point. | ||
- Deref patterns may interact with `&` and `&mut` patterns. | ||
- Future changes to reference types (partial borrows, language sugar for `Pin`, | ||
etc) may interact with match ergonomics. | ||
|
||
## Matching `&mut` behind `&` | ||
|
||
There is one notable situation where match ergonomics cannot be used, and | ||
explicit `ref` is required. Notably, this can occur where `&mut` is nested | ||
behind `&`: | ||
|
||
```rust | ||
// No way to avoid the `ref` here currently | ||
let &[&mut ref x] = &[&mut 42]; | ||
``` | ||
|
||
There are two strategies we could take to support this: | ||
|
||
- `&mut` patterns could match "behind" `&`. For example, in `let [&mut x] = &[&mut 42];`, | ||
the `&mut` pattern would match the `&mut` reference in the scrutinee, leaving | ||
`&` to be inherited and resulting in `x: &i32`. | ||
- This may not extend gracefully to future language features (partial borrows, | ||
for example) as it relies on reference types forming a total order. | ||
- The compiler could insert `&mut ref` in front of identifier patterns of type | ||
`&mut` that are behind an `&` pattern. For example, `let &[x] = &[&mut 42];` | ||
would be transformed into `let &[&mut ref x] = &[&mut 42];`. | ||
- The full desugaring would be more complicated, as it would need to handle | ||
`@` patterns. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is not a breaking change, I would highly advice this be split from this RFC. Too much changes at the same time make it harder to reason about; the time pressure from the edition is also exists, and we should not try to push changes with the edition ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mention this in a footnote, but
&
matching against&mut
alleviates the downsides of the "noref mut
behind&
" part of the RFC, which motivates them being included together.