Skip to content

Commit cbf6f22

Browse files
Add match ergonomics 2024 RFC
1 parent 55a275c commit cbf6f22

File tree

1 file changed

+337
-0
lines changed

1 file changed

+337
-0
lines changed

text/3627-match-ergonomics-2024.md

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
- Feature Name: `ref_pat_eat_one_layer_2024`
2+
- Start Date: 2024-05-06
3+
- RFC PR: [rust-lang/rfcs#3627](https://github.com/rust-lang/rfcs/pull/3627)
4+
- Rust Issue: [rust-lang/rust#123076](https://github.com/rust-lang/rust/issues/123076)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Various changes to the match ergonomics rules:
10+
11+
- On edition ≥ 2024, `&` and `&mut` patterns only remove a single layer of
12+
references.
13+
- On edition ≥ 2024, `mut` on an identifier pattern does not force its binding
14+
mode to by-value.
15+
- On edition ≥ 2024, `&` patterns can match against `&mut` references.
16+
- On all editions, the binding mode can no longer ever be implicitly set to
17+
`ref mut` behind an `&` pattern.
18+
19+
# Motivation
20+
[motivation]: #motivation
21+
22+
Match ergonomics have been a great success overall, but there are some surprising
23+
interactions that regularly confuse users.
24+
25+
- `mut` resets the binding mode to by-value, which users do not expect; the
26+
mutability of the binding seems like a separate concern from its type
27+
(<https://github.com/rust-lang/rust/issues/105647>,
28+
<https://github.com/rust-lang/rust/issues/112545>)
29+
- `&` and `&mut` patterns must correspond with a reference in the same position
30+
in the scrutinee, even if there is an inherited reference present. Therefore,
31+
users have no general mechanism to "cancel out" an inherited reference
32+
(<https://users.rust-lang.org/t/reference-of-tuple-and-tuple-of-reference/91713/6>,
33+
<https://users.rust-lang.org/t/cannot-deconstruct-reference-inside-match-on-reference-why/92147>,
34+
<https://github.com/rust-lang/rust/issues/50008>,
35+
<https://github.com/rust-lang/rust/issues/64586>)
36+
- When an `&` or `&mut` pattern is used in a location where there is also an
37+
inherited reference present, both are stripped; adding a single `&` to the
38+
pattern can remove two `&`s from the type of the binding.
39+
40+
# Guide-level explanation
41+
[guide-level-explanation]: #guide-level-explanation
42+
43+
Match ergonomics works a little differently in edition 2024 and above.
44+
45+
## Matching against inherited references
46+
47+
In all editions, when you match against an `&` or `&mut` reference with the type
48+
of its referent, you get an "inherited reference": the binding mode of
49+
"downstream" bindings is set to `ref` or `ref mut`.
50+
51+
```rust
52+
// Unchanged from old editions:
53+
// `x` "inherits" the `&` from the scrutinee type.
54+
let [x] = &[42];
55+
let _: &u8 = x;
56+
```
57+
58+
In edition 2024 and above, an `&` or `&mut` pattern can match against this
59+
inherited reference, consuming it. A pattern that does this has no other effect.
60+
61+
```rust
62+
// New in edition 2023:
63+
// `&` pattern consumes inherited `&` reference.
64+
let [&x] = &[42];
65+
let _: u8 = x;
66+
```
67+
68+
## `&` matches against `&mut`
69+
70+
In edition 2024 and above, `&` patterns can match against `&mut` references
71+
(including "inherited" references).
72+
73+
```rust
74+
let &foo = &mut 42;
75+
let _: u8 = foo;
76+
```
77+
78+
## `mut` no longer strips the inherited reference
79+
80+
In older editions, `mut` on a binding "stripped" the inherited reference:
81+
82+
```rust
83+
// Old editions
84+
let (x, mut y) = &(true, false);
85+
let _: (&bool, bool) = (x, y);
86+
```
87+
88+
This no longer happens on edition ≥ 2024.
89+
90+
91+
```rust
92+
// Edition ≥ 2024
93+
let (x, mut y) = &(true, false);
94+
let _: (&bool, &bool) = (x, y);
95+
```
96+
97+
# Reference-level explanation
98+
[reference-level-explanation]: #reference-level-explanation
99+
100+
This explanation assumes familiarity with the current match ergonomics rules,
101+
including the "default binding mode" terminology. Refer to [RFC 2005](./2005-match-ergonomics.md#detailed-design).
102+
103+
## Edition 2024: `&` patterns can match against `&mut` references
104+
105+
`&` patterns can match against `&mut` references.
106+
107+
```rust
108+
let &foo = &mut 42;
109+
let _: u8 = foo;
110+
```
111+
112+
However, the `ref mut` binding mode cannot be used behind such patterns.
113+
114+
```rust
115+
let &ref mut foo = &mut 42;
116+
// ^~ERROR: replace `&` with `&mut`
117+
let _: &mut u8 = foo;
118+
```
119+
120+
## Edition 2024: `&` and `&mut` can match against inherited references
121+
122+
When the default binding mode is `ref` or `ref mut`, `&` and `&mut` patterns can
123+
reset it. `&` patterns will reset either `ref` or `ref mut` binding modes to
124+
by-value, while `&mut` can only reset `ref mut`. An `&` or `&mut` pattern that
125+
resets the binding mode in this way has no other effect.
126+
127+
```rust
128+
let [&x] = &[3u8];
129+
let _: u8 = x;
130+
131+
let [&mut x] = &mut [3u8];
132+
let _: u8 = x;
133+
134+
let [&x] = &mut [3u8];
135+
let _: u8 = x;
136+
137+
//let [&mut x] = &[3u8]; // ERROR
138+
```
139+
140+
`&` patterns are otherwise unchanged from older editions.
141+
142+
```rust
143+
let &a = &3;
144+
let _: u8 = a;
145+
146+
//let &b = 17; // ERROR
147+
```
148+
149+
If the default binding mode is `ref`, then `&mut` patterns are forbidden. If it
150+
is by-value, then they have the same effect as on older editions.
151+
152+
```rust
153+
//let [&mut x] = &[&mut 42]; // ERROR
154+
155+
// Unchanged from old editions
156+
157+
let &mut x = &mut 3;
158+
let _: u8 = x;
159+
160+
let &mut x = &mut &mut 3;
161+
let _: &mut u8 = x;
162+
163+
let &mut x = &mut &&mut 3;
164+
let _: &&mut u8 = x;
165+
166+
//let &mut x = &&mut 3; // ERROR
167+
```
168+
169+
## Edition 2024: `mut` does not reset binding mode to by-value
170+
171+
In the new edition, `mut` no longer resets the binding mode to by-value.
172+
Therefore, it is possible to have a mutable by-reference binding. (An explicit
173+
syntax for this is left to a future RFC.)
174+
175+
```rust
176+
let &[mut a] = &[42];
177+
a = &47;
178+
```
179+
180+
## All editions: the default binding mode is never set to `ref mut` behind an `&` pattern or reference
181+
182+
The binding mode is set to `ref` instead in such cases. (On older editions, this
183+
allows strictly more code to compile.)
184+
185+
```rust
186+
// All editions
187+
188+
let &[[a]]; = &[&mut [42]];
189+
let _: &u8 = a;
190+
191+
let &[[a]]; = &mut [&mut [42]];
192+
let _: &u8 = a;
193+
```
194+
195+
```rust
196+
// Edition ≥ 2024
197+
198+
let &[[&a]]; = &[&mut [42]];
199+
let _: u8 = a;
200+
201+
//let &[[&mut a]]; = &[&mut [42]]; // ERROR
202+
```
203+
204+
# Migration
205+
[migration]: #migration
206+
207+
This proposal, if adopted, would allow the same pattern to have different
208+
meanings on different editions:
209+
210+
```rust
211+
let [&a] = &[&0u8]; // `a` is `u8` on edition ≤ 2021, but `&u8` on edition ≥ 2024
212+
let [mut a] = &[0u8]; // `a` is `u8` on edition ≤ 2021, but `&u8` on edition ≥ 2024
213+
```
214+
215+
Instances of such incompatibilities appear to be common, but far from unknown
216+
(20 cases in `rustc`, for example). The migration lint for the feature entirely
217+
desugars the match ergonomics of the affected pattern. This is necessary to
218+
produce code that works on all editions, but it means that adopting the new
219+
rules could require editing the affected patterns twice: once to desugar the
220+
match ergonomics before adopting the new edition, and a second time to restore
221+
match ergonomics after adoption of the new edition.
222+
223+
# Drawbacks
224+
[drawbacks]: #drawbacks
225+
226+
This is a silent change in behavior, which is considered undesirable even
227+
over an edition.
228+
229+
# Rationale and alternatives
230+
[rationale-and-alternatives]: #rationale-and-alternatives
231+
232+
## Desirable property
233+
[desirable-property]: #desirable-property
234+
235+
The proposed rules for new editions uphold the following property:
236+
237+
> For any two nested patterns `$pat0` and `$pat1`, such that `$pat1` uses match
238+
> ergonomics only (no explicit `ref`/`ref mut`), and valid pattern match
239+
> `let $pat0($pat1(binding)) = scrut`, either:
240+
>
241+
> - `let $pat0(temp) = scrut; let $pat1(binding) = temp;` compiles, with the
242+
> same meaning as the original composed pattern match; or
243+
> - `let $pat0(temp) = scrut; let $pat1(binding) = temp;` does not compile, but
244+
> `let $pat0(ref temp) = scrut; let &$pat1(binding) = temp;` compiles, with the
245+
> same meaning as the original composed pattern match.
246+
247+
In other words, the new match ergonomics rules are compositional.
248+
249+
## `&` patterns matching against `&mut`
250+
251+
There are several motivations for allowing this:
252+
253+
- It makes refactoring less painful. Sometimes, one is not certain whether an
254+
unfinished API will end up returning a shared or a mutable reference. But as
255+
long as the reference returned by said API is not actually used to perform
256+
mutation, it often doesn't matter either way, as `&mut` implicitly reborrows
257+
as `&` in many situations. Pattern matching is currently one of the most
258+
prominent exceptions to this, and match ergonomics magnifies the pain because
259+
a reference in one part of the pattern can affect the binding mode in a
260+
different, faraway location[^nrmba]. If patterns can be written to always use
261+
`&` unless mutation is required, then the amount of editing necessary to
262+
perform various refactors is lessened.
263+
- It's intuitive. `&mut` is strictly more powerful than `&`. It's conceptually a
264+
subtype, and even if not implemented that way[^sub], coercions mean it often
265+
feels like one in practice.
266+
267+
```rust
268+
let a: &u8 = &mut 42;
269+
```
270+
271+
[^nrmba]: This is even more true in light of the new rule that prevents the
272+
default binding mode from being set to `ref mut` behind `&`.
273+
274+
[^sub]: Making `&mut` a subtype of `&` in actual implementation would require
275+
adding significant complexity to the variance rules, but I do believe it to be
276+
possible.
277+
278+
## `mut` not resetting the binding mode
279+
280+
Admittedly, there is not much use for mutable by-reference bindings. This is
281+
true even outside of pattern matching; `let mut ident: &T = ...` is not commonly
282+
seen (though not entirely unknown either). The motivation for making this change
283+
anyway is that the current behavior is unintuitive and surprising for users.
284+
285+
## Versus "eat-two-layers"
286+
287+
An alternative proposal would be to allow `&` and `&mut` patterns to reset the
288+
binding mode when not matching against a reference in the same position in the
289+
scrutinee, but to not otherwise change their behavior. This would have the
290+
advantage of not requiring an edition change. However, it would remain confusing
291+
for users. Notably, the [property from earlier](#desirable-property) would
292+
continue to not be satisfied.
293+
294+
In addition, this approach would lead to tricky questions around when
295+
mutabilities should be considered compatible.
296+
297+
(This alternative is currently implemented under a separate feature gate.)
298+
299+
# Unresolved questions
300+
[unresolved-questions]: #unresolved-questions
301+
302+
- How much churn will be necessary to adapt code for the new edition? There are
303+
0 instances of affected patterns in the standard library, and 20 in the
304+
compiler, but that is all the data we have at the moment.
305+
306+
# Future possibilities
307+
[future-possibilities]: #future-possibilities
308+
309+
- An explicit syntax for mutable by-reference bindings should be chosen at some
310+
point.
311+
- Deref patterns may interact with `&` and `&mut` patterns.
312+
- Future changes to reference types (partial borrows, language sugar for `Pin`,
313+
etc) may interact with match ergonomics.
314+
315+
## Matching `&mut` behind `&`
316+
317+
There is one notable situation where match ergonomics cannot be used, and
318+
explicit `ref` is required. Notably, this can occur where `&mut` is nested
319+
behind `&`:
320+
321+
```rust
322+
// No way to avoid the `ref` here currently
323+
let &[&mut ref x] = &[&mut 42];
324+
```
325+
326+
There are two strategies we could take to support this:
327+
328+
- `&mut` patterns could match "behind" `&`. For example, in `let [&mut x] = &[&mut 42];`,
329+
the `&mut` pattern would match the `&mut` reference in the scrutinee, leaving
330+
`&` to be inherited and resulting in `x: &i32`.
331+
- This may not extend gracefully to future language features (partial borrows,
332+
for example) as it relies on reference types forming a total order.
333+
- The compiler could insert `&mut ref` in front of identifier patterns of type
334+
`&mut` that are behind an `&` pattern. For example, `let &[x] = &[&mut 42];`
335+
would be transformed into `let &[&mut ref x] = &[&mut 42];`.
336+
- The full desugaring would be more complicated, as it would need to handle
337+
`@` patterns.

0 commit comments

Comments
 (0)