# Design Meeting: Match Ergonomics 5
## Context
At the beginnings of rust, patterns had to correspond to the matched type reference-for-reference, with patterns like `&(x, ref y)`. This form is what we call a "fully explicit"/"fully desugared" pattern. `x`/`ref x`/`ref mut x` is called the "binding mode" of a binding.
[RFC 2005 "Match ergonomics"](https://rust-lang.github.io/rfcs/2005-match-ergonomics.html) allowed patterns to omit `&`/`&mut`, causing a "default" binding mode to be deduced from the type. That default can still be overriden with an explicit `ref x`/`ref mut x`.
After many years of experience with RFC2005 match ergonomics, two issues have come up many times, prompting us to rethink match ergonomics:
- The surprising ["`mut` resets binding mode"](https://github.com/rust-lang/rust/issues/105647) behavior;
- If `SomePattern(x)` gives `x: &T`, it's not always possible to write `SomePattern(&x)` to get `x: T` ([relevant issue](https://github.com/rust-lang/rust/issues/64586)).
## Current status
We have had a fair amount of previous discussions on the topic:
- 2023-12-13 design meeting: https://hackmd.io/YLKslGwpQOeAyGBayO9mdw
- 2024-05-15 design meeting: https://hackmd.io/9SstshpoTP60a8-LrxsWSA
- 2024-06-07 design meeting: https://hackmd.io/_ey51TmXRqyHq1fQ_t9pmQ
- 2024-09-18 accidental design meeting: https://hackmd.io/@rust-lang-team/ryw4jv_a0#Vibe-check-Stabilize-RFC-3627
- Accepted RFC3627: https://github.com/rust-lang/rfcs/pull/3627
- Proposal to amend RFC3627: https://github.com/rust-lang/rust/issues/130501
- TC's big book of match ergonomics proposals: https://hackmd.io/zUqs2ISNQ0Wrnxsa9nhD0Q
- My personal document with some design axioms: https://hackmd.io/aL5FRz-QTc6K0qtUzPoU9A
- We implemented a [maximally future-compatible](https://github.com/rust-lang/rust/pull/131381) version of match ergonomics in edition 2024. It restricts what's allowed so that we can backwards-compatibly implement the changes that follow.
- So far I've not heard of it causing much churn (note that the ["migrate compiler to edition 2024"](https://github.com/rust-lang/rust/pull/129636) PR was made _before_ we implemented that change, so we can't use it as data).
Our consensus so far (AFAICT):
- We're ready to alter how match ergonomics work (this leaves the behavior of fully-desugared patterns unchanged);
- We want to fix ["`mut` resets binding mode"](https://github.com/rust-lang/rust/issues/105647);
- If `SomePattern(x)` gives `x: &T`, we want `SomePattern(&p)` to be allowed ([relevant issue](https://github.com/rust-lang/rust/issues/64586)), and same for `&mut T`/`&mut p`;
- We settled on "eat-one-layer" for the following case:
```rust!
if let Some(x) = &Some(&0) {
// `x: &&i32`
}
if let Some(&x) = &Some(&0) {
// Today aka eat-two-layers: `x: i32`
// eat-one-layer: `x: &i32`
}
```
- As a convenience bonus, we want `&p` to be allowed on `&mut T`.
Question: do we agree on that?
There remain undecided points around:
- Further edge-cases within eat-one-layer;
- Should we allow `let Some(mut x) = &Some(0)`?
- Should we allow `let Some(ref x) = &Some(0)`? what about `let Some(ref x) = &mut Some(0)`?
- "Rule 3" i.e. should we downgrade `&mut` inherited references when we know they'll cause a borrow error;
I would like to discuss these and find consensus on them.
## Edge cases of eat-one-layer
"eat-one-layer" is the idea that:
- if `let SomePattern(x): U` gives `x: &T`, `let SomePattern(&p): U` should be allowed for appropriate `p`;
- if `let SomePattern(x): U` gives `x: &&T`, `let SomePattern(&x): U` should give `x: &T` regardless of `U` (instead of sometimes `x: T` as is the case in stable rust).
This by itself leaves some edge-cases unspecified. Some terminology first: in the presence of a non-`move` default binding mode (aka an inherited reference), there are two types at play:
```rust!
let [x]: &[T] = ...;
// ^ in-memory type
// x: &T
// ^ "inherited" reference
// ^^ user-visible type
```
Here is the choice to make: should a `&p`/`&mut p` pattern be generally matched against the in-memory type, or the user-visible type?
Some points to consider:
- Representative cases where the two options differ ([see in my tool, in-memory on the left and user-visible on the right; click on a row to see an explanation of the results](https://nadrieril.github.io/typing-rust-patterns/?q=%5B%26%26mut+x%5D%3A+%26%5B%26mut+T%5D&compare=true&opts2=AQEBAAEBAQEBAgIAAQEBAAEBAAABAAA%3D&opts1=AQEBAgEBAQEBAgIAAAAAAAAAAAAAAAA%3D&mode=compare&do_cmp=true&ty_d=3)):
```rust!
let [x]: &[&mut T] = ...;
// both : `x: &&mut T`
let [&mut x]: &[&mut T] = ...;
// in-memory : `x: &T`
// user-visible: type error
let [&x]: &[&mut T] = ...;
// in-memory : `x: &T` because `&p` is allowed to match `&mut T`
// user-visible: `x: &mut T` (can't copy `&mut`)
let [x]: &mut [&T] = ...;
// both : `x: &mut &T`
let [&mut x]: &mut [&T] = ...;
// in-memory : type error, or fallback to `x: &T`
// user-visible: `x: &T`
let [&x]: &mut [&T] = ...;
// in-memory : `x: &mut T` or `x: &T` depending on rule 3
// user-visible: `x: &T` because `&p` can match `&mut`
let [&(mut x)]: &[&T] = ...;
// in-memory : error on `mut` binding with inherited reference because we keep the outer inherited reference
// user-visible: `x: &T` because we eat the outer inherited reference
```
- `eat-one-layer` is defined and justified in terms of the user-visible type;
- Matching the in-memory type first is what has been called "primacy of structural matching"; it's what RFCs 2005 and 3627 do;
- "in-memory first" implies that manual monomorphization does not preserve type-checking of patterns:
```rust!
fn first<T>(slice: &[T]) -> Option<&T> {
match slice {
[] => None,
[&ref x, ..] => Some(x),
}
}
// =>
fn first_mono(slice: &[&u8]) -> Option<&&u8> {
match slice {
[] => None,
// `ref` is under a non-`move` default binding mode. Depending on what we choose below for this case:
// - option 1: this is an error by itself;
// - option 2: type error because `x: &u8`;
// - option 3: borrow error because `x: &&u8` was obtained from `&&*(*slice)[0]`, which borrows a temporary.
[&ref x, ..] => Some(x), // ERROR
}
}
```
## `mut x` on inherited references
Question: should we allow `let [mut x]: &[T] = ...;`? (In 2024 so far, this is an error). Per previous considerations, we want `x: &T`; this requires either:
1. Stabilizing a syntax like `mut ref x` or `ref (mut x)` so that this pattern can be desugared;
2. Giving up on the ability to desugar match ergonomics to stable rust syntax.
Points to consider:
- Option 2 make match ergonomics able to represent more patterns than non-ergonomic patterns;
- Desugaring is useful as a way to explain what match ergonomics "really does", but the explanation still mostly works even with that edge case;
- The two options are identical wrt implementation complexity, in either case the compiler will have to represent the `mut ref x` case internally.
## `ref x` on inherited references
Question: should we allow `let [ref x]: &[T] = ...;` etc? (In 2024 so far, this is an error). There are roughly three options:
1. Always throw an error.
1. Reset the binding mode i.e. override the mutability of the inherited reference; gives `x: &T` here.
1. Create a temporary and borrow it; gives `x: &&T` here.
Points to consider:
- Option 3 is at odds with how Rust usually does things;
- Option 2 is useful for the case of downgrading an inherited `&mut` to `&`:
```rust!
let [ref x]: &mut [T] = <expr>;
// With option 2: `x: &T`
// Without option 2, one would have to write instead:
let [&(ref x)]: &mut [T] = <expr>;
// or immutably borrow the scrutinee if applicable:
let [x]: &&mut [T] = &<expr>;
```
- Options 1 makes pattern macros (particularly with [guard patterns](https://rust-lang.github.io/rfcs//3637-guard-patterns.html)) harder to write; with options 2 and 3, `ref x` is always allowed and gives a `&T`;
- Option 3 is more uniform for the purposes of type inference: it treats inherited and normal references the same. so "`ref` always adds a layer of reference", and conversely "removing `ref` always removes a layer of reference".
I propose option 2.
## Rule 3
Take the following:
```rust!
let &[x]: &&mut [T] = ...;
// Desugars to:
let &&mut [ref mut x]: &&mut [T] = ...; // borrow error
```
Here `[x]` is matched against type `&mut [T]`, which causes an inherited `&mut T` reference. Because we're under a shared reference, we end up with a borrow error.
In RFC3627, TC proposed "rule 3": if the pattern is `&p`, while processing `p` downgrade all inherited references from `&mut` to `&`. The example would desugar instead to:
```rust!
let &&[ref x]: &&mut [T] = ...;
```
Some points to consider:
- This avoids some borrow errors;
- If we allow option 2 for `ref x`, adding a `ref` would suffice to fix the borrow errors in question instead;
- This does not solve all similar-looking errors, e.g. `let &[x]: &[&mut T]` is still an error;
- This is only relevant when using match ergonomics under explicitly handled references. It does not apply if a pattern is fully explicit or if a pattern is fully match-ergonomic or if a pattern starts ergonomic then adds some explicit dereferences.
- [Cases where the two options differ](https://nadrieril.github.io/typing-rust-patterns/?q=%5B%26%26mut+x%5D%3A+%26%5B%26mut+T%5D&compare=true&opts2=AQEBAAEBAQEBAAEAAAAAAAAAAAAAAAA%3D&opts1=AQEBAAEBAQABAAEAAAAAAAAAAAAAAAA%3D&mode=compare&do_cmp=true&ty_d=3&pat_d=3), without rule3 the left and with rule3 on the right, based on my otherwise prefered choices for the options above. You may increase type depth to have more examples.
- `let &[&mut x]: &&mut [T]` becomes a type error;
- `let &&mut [x]: &&mut [T]` is still allowed, which may feel inconsistent;
- This makes typechecking less "local"/"compositional": this requires an extra bit of state when formalizing and implementing match ergonomics;
- We can leave this undecided for now by erroring in cases where we would downgrade to `&`.