owned this note
owned this note
Published
Linked with GitHub
---
title: "Design meeting 2023-12-13: Match ergonomics 2024"
date: 2023-12-13
tags: ["T-lang", "design-meeting", "minutes"]
discussion: https://rust-lang.zulipchat.com/#narrow/stream/410673-t-lang.2Fmeetings/topic/Design.20meeting.202023-12-13
url: https://hackmd.io/YLKslGwpQOeAyGBayO9mdw
---
# Design meeting: Match ergonomics 2024
[Reading note: What follows first is the original presentation of the proposal. After that, [here](https://hackmd.io/YLKslGwpQOeAyGBayO9mdw#Appendix-A-A-slightly-formalized-restatement-of-the-above), a more concise and formalized restatement of the current behavior and the proposal is presented for interested readers.]
## Motivation
Two things come up regularly ([1](https://github.com/rust-lang/rust/issues/105647), [2](https://github.com/rust-lang/rust/issues/112545), [3](https://users.rust-lang.org/t/reference-of-tuple-and-tuple-of-reference/91713), [4](https://users.rust-lang.org/t/cannot-deconstruct-reference-inside-match-on-reference-why/92147), [5](https://github.com/rust-lang/rust/issues/50008), [6](https://github.com/rust-lang/rust/issues/64586), etc) with regards to match ergonomics:
1. `mut` resets the default binding mode, which is confusing.
```rust
match &Some(0) {
Some(x) => {} // x: &i32 here
Some(mut x) => {} // x: i32 here surprisingly
}
```
2. `&` cannot be used in patterns where one might expect.
```rust
match &Some(0) {
Some(x) => {} // x: &i32 here
Some(&y) => {} // Disallowed despite the type of `x` above
}
```
3. (I have not seen this come up but) Beyond 1., one cannot use `mut` uniformly to declare bindings mutable.
```rust
match Some(0) {
Some(mut x) => {} // mutable `x: i32`
Some(ref x) => {} // immutable `x: &i32`
// no way to get a mutable `x: &i32`
}
```
I propose to fix all three at once, particularly since together they make it possible to change 1 in such a way that `rustfix` can fix the backwards-incompatible cases. 1 would be changed over the 2024 edition.
## Current behavior
Here is the current behavior of match ergonomics in terms of the mental model I like:
* Matching on a place of type `&(A, B)` with `(<pat1>, <pat2>)` slips the reference into the tuple, making it morally `(&A, &B)`, after which matching continues recursively. Same for other non-reference, non-binding patterns.
```rust
if let (x, y) = &(0, 1) {
let _: &u32 = x;
let _: &u32 = y;
}
if let Some(x) = &Some(0) {
let _: &u32 = x;
}
```
* Moreover we keep at most one layer of such inherited references. It wouldn't make sense to do otherwise:
```rust
// No way to get a `x: &&u32` here since there isn't a `&u32` in memory
// it could point to.
if let Some(Some(x)) = &Some(&Some(0)) {
let _: &u32 = x;
}
```
* A `&<pat>` pattern is only allowed if the place has actual type `&T` (i.e. ignoring inherited references). It dereferences the layers of references at that place, namely: the inherited reference if any, plus the actual reference since the place has type `&T`. It then continues matching `<pat>` on the dereferenced place (of type `T`).
```rust
if let Some(&x) = Some(&0) {
let _: u32 = x;
}
if let Some(&x) = &Some(0) { //~ error: type mismatch
let _: u32 = x;
}
if let Some(&x) = &Some(&0) {
let _: u32 = x; // `&` dereferenced both references
}
// We have one layer of inherited ref, and an underlying place of type `&&T`.
if let Some(x) = &&Some(&&0) {
let _: &&&u32 = x;
}
// The `&` derefs the inherited ref plus one actual ref.
if let Some(&x) = &&Some(&&0) {
let _: &u32 = x;
}
```
* A `x` binding gets the type computed from the actual type of the place plus the inherited reference if there is one.
```rust
if let Some(x) = &Some(&0) {
let _: &&u32 = x;
}
```
* A `ref x` binding takes a reference to the current place if there was no inherited reference, otherwise does nothing.
```rust
if let &Some(ref x) = &Some(0) {
let _: &u32 = x;
}
if let &Some(ref x) = &Some(&0) {
let _: &&u32 = x; // `ref` took a reference to a place of type `&u32`
}
```
* As such, `&ref x` isn't identical to `x`; compared to `x`, it discards the inherited reference.
```rust
// Compare `x` and `&ref x`.
if let Some(x) = &Some(&0) {
let _: &&u32 = x;
}
// Here the `&` derefs both layers, so the `ref` can only recover one.
if let Some(&ref x) = &Some(&0) {
let _: &u32 = x;
}
```
* A `mut x` binding:
* Dereferences the inherited reference (if any) (that's the bit that's confusing in this model);
* Matches the underlying place with a normal binding `x`;
* Declares the binding `x` mutable.
```rust
// `mut x` dereferences the inherited reference
if let Some(mut x) = &Some(0) {
x = 1;
let _: u32 = x;
}
if let Some(mut x) = &Some(&0) {
x = &1;
let _: &u32 = x;
}
```
* All of this works identically for `&mut`/`ref mut`. Interactions between inherited `&mut` and `&` collapse to `&`.
```rust
// `&mut` behaves just like `&`
if let Some(Some(x)) = &mut Some(&mut Some(0)) {
let _: &mut u32 = x;
}
// Mixing `&` and `&mut` inherited references gives `&`.
if let Some(Some(x)) = &mut Some(&Some(0)) {
let _: &u32 = x;
}
```
* Of note: a `&<pat>` pattern is not allowed on a place of type `&mut T`; mutabilities must match here.
```rust
if let &Some(x) = &mut Some(0) { //~ error: type mismatch
let _: &u32 = x;
}
```
Some more examples at the [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a907683be96cf5c9180e1cca0d1f4351).
## Proposal
### `&<pat>` everywhere (non-breaking)
I propose the following extension: we remove the typecheck requirement on `&<pat>`/`&mut <pat>`; instead we allow these patterns whenever a corresponding binding would acquire a compatible reference type. The rule becomes:
* A `&<pat>` pattern dereferences the layers of references at that place, namely: the inherited reference if any, and the actual reference if the place has type `&T`/`&mut T`. It then continues matching `<pat>` on the dereferenced place. It is disallowed if there was nothing to dereference.
This would allow:
```rust
if let Some(Some(&x)) = &Some(&Some(0)) {
let _: u32 = x;
}
if let Some(&Some(x)) = &Some(Some(0)) {
let _: u32 = x;
}
if let Some(&x) = Some(0) { //~ error: type mismatch
let _: u32 = x;
}
if let Some(Some(&mut x)) = &mut Some(&mut Some(0)) {
let _: u32 = x;
}
if let &Some(x) = &mut Some(0) { // compatible reference types
let _: u32 = x;
}
```
Note: this only affects desugaring into non-match-ergonomic patterns. In particular this requires no changes to type- or borrow-checking. The desugaring algorithm likely doesn't even need to change much, without having looked into it I'd guess a `&` pattern on a non-reference type simply resets the binding mode to "move", e.g.:
```rust
// input
if let Some(Some(&x)) = &Some(&Some(0)) {
let _: u32 = x;
}
// desugared
if let &Some(&Some(x)) = &Some(&Some(0)) {
let _: u32 = x;
}
```
The fact that `&` "eats" all inherited references has been found confusing ([1](https://github.com/rust-lang/rust/issues/42640#issuecomment-386561381)). See discussion on alternatives below.
### `ref (mut x)` (or general `ref <pat>`) (non-breaking)
I propose we allow `ref (mut x)`/`ref mut (mut x)` to signify a `ref x`/`ref mut` binding that is allowed to be mutable. (Or `mut (ref x)`/`mut (ref mut x)` maybe?). This would allow:
```rust
if let Some(ref (mut x)) = Some(0) {
if *x == 0 {
x = &1; // x: &u32
}
}
let y = 1;
if let Some(ref mut (mut x)) = Some(0) {
if *x == 0 {
x = &mut y; // x: &mut u32
}
}
```
There's a more uniform alternative that I'm not really proposing but thought I'd mention: we could allow `ref`/`ref mut` not just on bindings but with arbitrary patterns. The rule would become:
* A `ref <pat>` adds takes a reference to the underlying place if there was no inherited reference, then continues matching with `<pat>`.
This would allow (on top of the above):
```rust
let mut opt = Some(0);
if let ref mut Some(x) = opt {
*x = 1;
}
```
### Non-dereferencing `mut x` (breaking)
I propose the following amendment: `mut x` now keeps the inherited reference. The rule becomes:
* A `mut x` binding matches matches the place with a normal binding `x` and declares it mutable.
This would allow:
```rust
if let Some(mut x) = &Some(0) {
let _: &u32 = x;
}
```
This would make the following no longer compile:
```rust
fn foo(opt: &Option<u32>) -> u32 {
match opt {
Some(mut x) => {
x += 1; //~ error: binary assignment operation `+=` cannot be applied to type `&u32`
x
}
None => 0
}
}
```
## Machine-applicable fix
Now, if we take all three proposals, then I claim that the following transformation preserves previous behavior:
* `mut x` without inherited reference works like before.
* `mut x` with inherited reference on a place not of type `&T` is changed to `&(mut x)`.
```rust
// old:
if let Some(mut x) = &Some(0) {
let _: u32 = x;
}
// new:
if let Some(&(mut x)) = &Some(0) {
let _: u32 = x;
}
```
* `mut x` with inherited reference on a place of type `&T` is changed to `&(ref (mut x))`.
```rust
// old:
if let Some(mut x) = &Some(&0) {
let _: &u32 = x;
}
// new:
if let Some(&(ref (mut x)) = &Some(&0) {
let _: &u32 = x;
}
```
## Alternate rules for the behavior of `&<pat>`
Taking for now that it doesn't make sense to keep more than one layer of inherited reference around (as explained [above](https://hackmd.io/YLKslGwpQOeAyGBayO9mdw?both=#Current-behavior)), there are only three possibilities for the behavior of `&<pat>`:
1. (stable) eat the inherited reference if any, and the actual reference (which must be present)
2. (eat-both-layers) eat the inherited reference if any, and the actual reference if any
3. (eat-one-layer) eat the inherited reference if any, xor the actual reference if any
1 is the current behavior; it requires an actual reference. eat-both-layers is the one I propose. eat-one-layer would be a breaking change since it would require `&&x` to copy out the `u32` here:
```rust
// With current Rust and eat-both-layers:
if let Some(x) = &Some(&0) {
let _: &&u32 = x;
}
if let Some(&x) = &Some(&0) {
let _: u32 = x;
}
// With behavior eat-one-layer:
if let Some(x) = &Some(&0) {
let _: &&u32 = x;
}
if let Some(&x) = &Some(&0) {
let _: &u32 = x;
}
if let Some(&&x) = &Some(&0) {
let _: u32 = x;
}
```
Note however that eat-one-layer would avoid the issues with deref patterns mentioned [below](https://hackmd.io/YLKslGwpQOeAyGBayO9mdw?both=#Deref-patterns).
## Proposed timeline
1. Implement the two non-breaking changes.
2. Warn when using a dereferencing `mut x`. Propose the fix above instead. The resulting code works identically on both editions.
3. Over the edition boundary, change the behavior of `mut x` as proposed above.
4. Code that hasn't applied the fix will fail to typecheck. The typecheck error can suggest the fix as well.
## Open questions
### Deref patterns
There may be some interaction with [deref patterns][], depending on choices we both make. Specifically:
- I assume they will provide some explicit pattern syntax to cause a `deref` to happen (even if this syntax is optional). Proposals include `deref <pat>`, `*<pat>` and `&<pat>`.
- If the chosen syntax is `&<pat>`, then deref patterns could be a breaking change wrt this proposal.
Take the following:
```rust
if let Some(&x) = &Some(Box::new(0)) {
/// ...
}
```
- Under current Rust, this is a type error.
- Under this proposal only, `x: Box<u32>`.
- Under deref patterns only, most likely `x: u32`.
- Under both if we take [option 2 (eat-both-layers)](https://hackmd.io/YLKslGwpQOeAyGBayO9mdw?both=#Alternate-rules-for-the-behavior-of-ampltpatgt), most likely `x: u32`.
- Under both if we take [option 3 (eat-one-layer)](https://hackmd.io/YLKslGwpQOeAyGBayO9mdw?both=#Alternate-rules-for-the-behavior-of-ampltpatgt), `x: Box<u32>`.
Essentially: if deref patterns lands first or we choose eat-one-layer, then deref patterns add no additional breaking changes. If we take option 2 it would be best if those could land in the same edition.
[deref patterns]: https://github.com/rust-lang/rust/issues/87121#issuecomment-1469352893
# Appendix A: A slightly formalized restatement of the above
TC: This section is a summary, analysis, and restatement of the current behavior and the proposal above. If you're feeling hazy on this and seeing the proposal broken down and analyzed further by a different author is helpful to you, feel free to review this appendix, otherwise skip to the bottom for questions and discussion.
## Current behavior
### Distribution
An outer reference is distributed to inner bindings:
```rust
let (a,) = &(A,);
-------------------- // a: &A
let (ref a,) = (A,);
```
### Reducing outer references
Multiple outer references are reduced to a single inner reference:
```rust
let (a,) = &&(A,);
-------------------- // a: &A
let (ref a,) = (A,);
```
### Preservation
Multiple inner references are preserved:
```rust
let (a,) = (&&A,);
--------------------- // a: &&A
let (ref a,) = (&A,);
```
Any remaining outer references are combined with the preserved inner references:
```rust
let (a,) = &(&A,);
--------------------- // a: &&A
let (ref a,) = (&A,);
```
### Reduction
An inner `&` in the pattern first removes any remaining outer reference, then removes an inner reference, and gives an error if there is not an inner reference to remove:
```rust
let (&a,) = &(&&A,);
-------------------- // a: &A
let (ref a,) = (A,);
```
```rust
let (&a,) = &(&A,);
------------------- // a: A
let (a,) = (A,);
```
```rust
let (&a,) = &(A,); //~ ERROR type mismatch
```
```rust
let (&a,) = (&&A,);
-------------------- // a: &A
let (ref a,) = (A,);
```
```rust
let (&a,) = (&A,);
------------------ // a: A
let (a,) = (A,);
```
```rust
let (&a,) = (A,); //~ ERROR type mismatch
```
### `mut` oddity
`mut` resets the binding mode. So:
```rust
let (mut a,) = &(&A,);
---------------------- // a: &A
let (ref a,) = (A,);
let mut a = a;
```
By contrast:
```rust
let (a,) = &(&A,);
--------------------- // a: &&A
let (ref a,) = (&A,);
```
### Distribution oddity
Even though:
```rust
let (a,) = &(A,);
-------------------- // a: &A
let (ref a,) = (A,);
```
This is an error:
```rust
let (&a,) = &(A,); //~ ERROR type mismatch
```
This is surprising because, without the `&` in the pattern, `a: &A`.
## Proposal
### `&<pat>` everywhere
We change the reduction rule as follows:
An inner `&` in the pattern removes the one remaining outer reference OR one inner reference. If no reference can be removed, it is an error.
```rust
let (&a,) = &(A,);
------------------ // a: A
let (a,) = (A,);
```
```rust
let (&a,) = (&A,);
------------------ // a: A
let (a,) = (A,);
```
```rust
let (&a,) = (A,); //~ ERROR type mismatch
```
But note that we might consider alternate rules and described [above](https://hackmd.io/YLKslGwpQOeAyGBayO9mdw#Alternate-rules-for-the-behavior-of-ampltpatgt) and [below](https://hackmd.io/YLKslGwpQOeAyGBayO9mdw#Plausible-alternative-reduction-rule).
### Support `ref (mut x)` / `ref mut (mut x)`
Currently we allow creating *immutable* bindings for references with `ref a`, and we allow creating mutable bindings with `mut a`, but we don't allow creating *mutable* bindings for references in one step. We would change the rules to allow this:
```rust
let (ref (mut a),) = (A,);
-------------------------- // mut a: &A
let (ref a,) = (A,);
let mut a = a;
```
```rust
let (ref mut (mut a),) = (A,);
------------------------------ // mut a: &mut A
let (ref mut a,) = (A,);
let mut a = a;
```
### Keep binding mode with `mut`
We would change the rules such that `mut` would make the binding mutable but not otherwise affect the binding mode. Consequently:
```rust
let (mut a,) = &(&A,);
---------------------- // mut a: &&A
let (ref a,) = (&A,);
let mut a = a;
```
This is a breaking change.
### `cargo fix` handling
For `mut a` bindings where there is an outer reference, perform type checking on the inner place. If the inner place is not a reference, then convert:
```rust
let (mut a,) = &(A,);
------------------------ // mut a: A
let (&(mut a),) = &(A,);
```
If the inner place is a reference, then convert:
```rust
let (mut a,) = &(&A,);
---------------------------- // mut a: &A
let (&ref (mut a),) = &(A,);
```
## Plausible alternative reduction rule
We could change the reduction rule to be:
When there is an inner `&` in the pattern, if there is a remaining outer reference, remove it, otherwise remove one inner reference. If no reference can be removed, it is an error.
This would be a breaking change (but might be less confusing).
```rust
let (&a,) = &(A,);
------------------ // a: A
let (a,) = (A,);
```
```rust
let (&a,) = (&A,);
------------------ // a: A
let (a,) = (A,);
```
```rust
let (&a,) = &(&A,);
------------------ // a: &A
let (ref a,) = (A,);
```
```rust
let (&a,) = (A,); //~ ERROR type mismatch
```
If we adopted this alternate rule, we could teach the rule simply as:
> First, remove all but one outer reference, then remove an equal number of `&`s as appear in the pattern.
Today's rule, and the other rule proposed here, are more complicated to describe.
This would also make `&ref` in the pattern into a no-op, which is what people might expect.
---
# Minutes
People: TC, Nadrieril, Josh, tmandry, waffle, compiler-errors, Urgau, pnkfelix, scottmcm
Minutes/driver: TC
## Which reduction rule / number of levels unwrapped by `&`-pat
TC: We should discuss which reduction rule we prefer.
waffle:
If the idea to remove the confusion of "why `x` has a reference type, but I can't use `&` in a pattern for it", I think we should also make `&` only remove a single level of references, because it further fixes the distinction between "inherited" and "actual" references.
Specifically, consider these two examples:
```rust
let (&x,) = &(&A,); // x: &A (if eats one layer sem),
// x: A (if eats both layers sem)
```
```rust
let (x,) = &(&A,); // x: &&A
let &x = x; // x: &A
```
([play](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3dbbcaa71d44968aac381b186a3e7845))
I somewhat strongly believe that they should be equivalent and `&`-pattern should be just the inverse of `&`-expression.
This of course has a downside of being a breaking change, but I think it's worth changing. Both for simpler explanation of the rules, `&ref` being noop, etc.
TC: If we go with the `eat-both-layers` rule, we'll be exposing a lot of people to the oddity that `&ref` is not a no-op through the code added for the edition conversion.
TC: If we go with `eat-one-layer`, that will be a no-op, and overall the rule will be easier to describe and teach.
Nadrieril: I want to note that "`&<pat>` everywhere" is orthogonal to eat-one-layer, since eat-one-layer only affects `&<pat>` on a place of type `&T`, i.e. what's already allowed on stable. We can decide the two things separately.
TC: It's true and a good point. But they'd both still need to be decided, and there's not much time before Rust 2024.
waffle: (Discussion as above.)
tmandry: I instinctly agree with the argument in favor of the XOR rule. But it's difficult to think through all of the effects.
Josh: People do get confused by the number of levels of reference. So there are arguments on both sides. Once you have multiple levels of references, you're in a potentially confusing state.
waffle: One of the problems of the OR rule is that you run into suprising things like `&ref`.
Josh: That does seem odd.
TC: The two most motivating things to me in favor of the XOR rule are 1) making `&ref` a no-op, and that 2) it would be easier to describe and teach.
waffle: I like the idea of making language that is easy to teach, it's good for everyone.
Nadri: Does anyone know why the current rule is what it is?
Josh: Not sure. But maybe it's OK if the rule is that you write it without `&` then bang in the `&`s until it compiles.
pnkfelix: I'd hope that we can aim higher than that.
Josh: Agreed. Part of match ergonomics is trying to do what the author means; we shouldn't expect them to get the references *exactly* right.
waffle: With the OR rule, you need the `&ref` pattern sometimes, and that's a hard thing to discover.
scottmcm: Can we use the iterator filter pattern to think of use cases here? Where, e.g., do we end up with three references?
Josh: One place it comes up often is iterator filter where your base type is e.g. `&str`.
waffle: But in that case, you wouldn't want to remove all these because then you'd have an unsized type.
Nadri: Even just two references is an interesting case due to, e.g. `Copy`.
scottmcm: Having people type out multiple `&`s might be OK since these situations are hopefully uncommon. The only place I know of `&&T` that's not from generic types is `&&self.last_field` in the derived `Debug`, to deal with unsized fields when getting `dyn Debug`.
Josh: There are good reasons for `&mut &mut _`. There's almost never a good reason for `&&&T` or even `&&T`; it only comes up because of type inference, and if someone were writing the type and realized what it was they'd want it to be a single reference.
waffle: Because we remove all but one of the outer references, that tends toward reducing the normal level of references.
tmandry: I agree with the framing we should write out the common cases of where this comes up and look at it through that lens.
tmandry: If you have only one or two references it's easier to keep track of which combinators you want.
TC: Are we saying, in terms of framing, that the more uncommon multiple levels of references are, the more OK we are with people having to write multiple `&`s?
Josh: If only one or two are most common, then we should make the behavior the most clear in those cases.
scottmcm: I'd trade having to write `&&` for having to write `&ref`. In fact, even if that means having to write `&&&&`, it's OK since `&&&&` doesn't really come up.
Josh: Agreed.
tmandry, TC: +1.
## Destructuring mut references with `&`-pat
waffle:
This document currently [proposes](#ampltpatgt-everywhere-non-breaking) to allow the following example:
```rust
if let &Some(x) = &mut Some(0) { // compatible reference types
let _: u32 = x;
}
```
i.e. matching `&mut T` with `&<pat>`.
I'm somewhat opposed to that, my points are:
1. It's nice to have the inversion of "syntax used to construct is also used to destruct"
2. It might be nice to have a bit of a highlight "hey, a mutable reference was here"
3. We can always do this later, it's not a breaking change
Nadrieril: I'm mildly in favor because we generally allow using a `&mut T` where a `&T` is needed, and more flexible refactorings, but not strongly.
waffle: I feel like with refactoring specifically it's nice to know that if you remove `&` you get a `&` inside:
```rust
if let &Some(x) = some_function() {
// x: T
}
// =>
if let Some(x) = some_function() {
// x: &T
}
```
Although this probably doesn't matter too much in practice.
TC: Note, for comparison, that we currently allow this on stable:
```rust
let (&a,) = &mut (&&A,); // a: &A
```
waffle: Oh. :(
## Mix of match ergonomics and what came before?
Josh: When we transitioned to match ergonomics, there was a general feeling among those favoring match ergonomics that `ref` and `ref mut` were *mostly* legacy (other than the fact that we don't infer `&mut`). This was, in part, because those keywords were viewed as confusing, being the "inverse" of a reference/mut in a type. (Writing `&` in a pattern *removes* a level of reference, but writing `ref` in a pattern *adds* one.)
It feels like the second and third proposals here (regarding uses of `mut` and `ref (mut)` and similar) are reintroducing mechanisms where you need to use these keywords rather than being able to "fully" use match ergonomics.
I'm wondering if there's a mechanism we could use that would lean *more* into match ergonomics and avoid the need for these keywords at all. Concretely: we sometimes need a way to say "I want to match at this point *using this type*", and if we had a syntax for that we might not need `ref`/`mut` anymore. For example:
```rust
match Some(source) {
Some(x: &mut _) => {} // x is now `&mut T`
}
match &Some(0) {
Some(x: u32) => {} // x is now `u32` rather than `&u32`
}
```
That exact syntax probably doesn't work (there's a reason we abandoned the ascription syntax), but something *like* it seems like it'd let people *just* think about types rather than types *and* the pattern inverses.
TC: It's worth considering whether we were simply wrong about the idea that that `ref` would no longer be needed. In my own code, I find that `ref` is often still needed or is the clearest way to express things.
TC: Also, perhaps one of the reasons that `ref` was surprising was that it couldn't be used consistently (e.g. the lack of `ref (mut x)`). Maybe making it more consistently available will help people to build a correct mental model.
Josh: Part of the point of match ergonomics was so that people didn't have to build that mental model. (I had the same position you (TC) did back when match ergonomics was first proposed, and didn't like the level of implicitness, and pushed for preserving the ability to write it the explicit way. I no longer think that was the right answer, and I wonder if we could have gotten somthing better if I hadn't pushed for that.)
TC: +1. I'm in favor of pushing match ergonomics as far as it can be pushed. It's more that we might want to do that and *also* make the explicit form as consistent and useful as possible.
Nadrieril: I find that the lack of `&<pat>` everywhere is one of the reasons that makes me use `ref`. E.g.
```rust
// Today:
match *thing {
Some(Foo { copy_a, copy_b, ref noncopy_c }) => {
// I want to copy `copy_a` and `copy_b` and get `noncopy_c` by ref.
}
}
// With `&<pat>` everywhere:
match thing {
Some(Foo { ©_a, ©_b, noncopy_c }) => {
...
}
}
```
waffle: In my quest to remove `ref`s from the compiler where match ergonomics are enough I've seen quite a few places where with `ref`s the code is readable. Examples are similar to what Nadrieril wrote above -- when most patterns want copy, using `ref` for the few patterns that can't use copy is nice.
Josh: +1 for the change to make `&` strip a level of reference, that one seems consistent. I'm talking more about the other two changes.
Nadrieril: The other two are about consistency of `mut x`, which shouldn't affect what kind of code people write. I don't think it nudges people to use `ref` more.
Josh: @Nadrieril: I'm not sure it nudges people to use ref and mut *less*, though.
Nadrieril: `mut` feels distinct from `ref` and `ref mut`.
Josh: I agree, and I'm wondering if we should keep `mut` *just* meaning "make this place mutable", without the added confusion of needing "make a mutable reference".
Nadrieril: You mean deprecating `ref mut`? I'm confused. `mut x` indeed only means "make this binding mutable".
scottmcm: I often see this:
> Nadrieril: I find that the lack of `&<pat>` everywhere is one of the reasons that makes me use `ref`.
scottmcm: I also think I asked a similar question in the section below and that may help to guide us.
waffle: We can't fully get rid of `ref` because it's useful when you have a lot of things on the same level, e.g. a lot of fields of the same structure, and you want most of them to be copied and some of them to be referenced. With this proposal there are maybe less cases of this, but the need for `ref` may be fundamental.
Nadri: Josh, is your concern really about the edition transition code?
Josh: It think it's more about defining more interactions between `ref` and `mut` in ways that make logical sense, but it feels uncomfortable that people would need to write this and couldn't write something simpler.
scottmcm: This still just applies to bindings and not patterns, right?
Nadri: Correct.
Nadri: And keep in mind, this is to keep the edition transition smooth. I wouldn't suggest it if it wasn't needed for that.
## `&ref` prevalence?
scottmcm: Above there's this example:
```rust
// old:
if let Some(mut x) = &Some(&0) {
let _: &u32 = x;
}
// new:
if let Some(&(ref (mut x)) = &Some(&0) {
let _: &u32 = x;
}
```
Seeing `&ref` like that makes me slightly sad. Is this something you estimate would be common? Or is it an artifact of the fix that wouldn't generally be used in code that normal people would end up writing?
Nadrieril: I wanted a mechanical and local refactoring to prove that we can. In general I think we can do better, e.g. here:
```rust
// new:
if let &Some(mut x) = &Some(&0) {
let _: &u32 = x;
}
```
I don't know of often we'd need the ugly version, I'd have to try on real code.
scottmcm: This may be what was discussed above.
Nadri: I'm hoping to be able to think of smarter transition logic that would need fewer of these inserted.
tmandry: This aspect of the proposal actually makes sense to me on its own, aside from the transition. I've had situations where this was needed and where it would have made things more clear.
TC: +1.
Josh: Agree that it seems useful.
Josh: It may be worth thinking about what we would want to drive toward in Rust 2027.
TC: It's worth keeping in mind that time is short before Rust 2024, and in terms of incremental progress, if we don't do anything here, the current situation isn't great either.
Josh: Complete agreement.
## `mut` and attaching to bindings
scottmcm: to this question
> (Or `mut (ref x)`/`mut (ref mut x)` maybe?)
I don't think it's `mut (ref x)`, because `mut` attaches to *bindings* not to *patterns*.
See also how
```rust
let mut (a, b) = (1, 2);
```
doesn't compile because `mut` can't apply to a pattern like that.
(IIRC there was an RFC that we rejected about that, so `mut` should stay next to the binding here too.)
Oh, `ref` is also attached to bindings today? Maybe what I'm saying here doesn't make sense then...
Josh: We've had proposals in the past to allow:
```rust
let mut (a, b) = (1, 2);
```
It does make some logical sense. We may or may not want to accept it, of course.
scottmcm: I recall an RFC that included this that we didn't accept.
## Musing on `ref(mut x)`
scottmcm: In expressions, we have a distinction between `(a)` -- which doesn't do anything -- vs `{a}` -- which does something. I wonder if there's something in here about finding another way to do things that don't have the distinction between `foo (bar x)` and `foo bar x`.
Josh: It does seem like having `ref mut x` and `ref (mut x)` mean two different things is extra confusing, and would be a first.
waffle: The parentheses here don't have an meaning. It's just grouping, as with binary operations. It feels OK here.
scottmcm: Binary operations feels different to me because those have associativity questions, and prefix operations don't have that.
Josh: In this context, the parentheses feel almost like a hint to the "lexer", in the sense that `ref mut` is one compound and the parentheses split it.
TC: If we agree on the desirability and semantics, probably no-one here would object to any reasonable syntax.
Josh: Since it's for the transition, we could make it something more "transition-y".
## Counting rule for references?
scottmcm: I didn't check for whether this is discussed, but is the way that `let Some(x) = &ₙSome(&ₘ0)` gives x be `max(n, m)` references or `min(n, m)` references pr `n+m` references? (This is more a self-note that I should read the doc again to think about this.)
Nadri: We have to collapse the outer references for fundamental semantic reasons.
scottmcm: Makes sense.
## Where do we go from here?
TC: What's the next step?
waffle: We talked about a narrow scope of questions. Perhaps that means that everything we didn't talk about are places that we actually have consensus.
Nadri: Do we think that people agree that having `&`-patterns everywhere?
scottmcm: Yes, I want that.
Nadri: Do we want to fix the weird behavior that `mut` resets the binding mode?
waffle: It feels like that should also be "yes". It probably didn't get discussion since probably no-one disagrees.
Josh: Agreed that the ones we didn't talk about are probably fine, though of course we'll still need further process.
Nadri: That means that I can start doing crater runs and put further effort into it.
Josh: You might want to make a quick note about what things you think there's likely consensus on because they didn't come up, and get confirmation (e.g. a quick emoji react) before diving in.
*Consensus*: We may have agreement about the goals here. We probably want `&` patterns everywhere, and we probably want to fix the problem of `mut` resetting the binding mode, i.e. to make it such that adding `mut` doesn't change the type of the binding. We'll want to write that out in Zulip or on the issue and verify that consensus with the fuller group. With agreement on that goal, Nadri can invest further time for writing code, performing crater runs, etc.
all: Also, huge thanks to Nadri for digging into this and pushing it forward.
(The meeting ended here.)