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" 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:
mut
resets binding mode" behavior;SomePattern(x)
gives x: &T
, it's not always possible to write SomePattern(&x)
to get x: T
(relevant issue).We have had a fair amount of previous discussions on the topic:
Our consensus so far (AFAICT):
mut
resets binding mode";SomePattern(x)
gives x: &T
, we want SomePattern(&p)
to be allowed (relevant issue), and same for &mut T
/&mut p
;&p
to be allowed on &mut T
.Question: do we agree on that?
There remain undecided points around:
let Some(mut x) = &Some(0)
?let Some(ref x) = &Some(0)
? what about let Some(ref x) = &mut Some(0)
?&mut
inherited references when we know they'll cause a borrow error;I would like to discuss these and find consensus on them.
"eat-one-layer" is the idea that:
let SomePattern(x): U
gives x: &T
, let SomePattern(&p): U
should be allowed for appropriate p
;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:
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:
eat-one-layer
is defined and justified in terms of the user-visible type;mut x
on inherited referencesQuestion: 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:
mut ref x
or ref (mut x)
so that this pattern can be desugared;Points to consider:
mut ref x
case internally.ref x
on inherited referencesQuestion: should we allow let [ref x]: &[T] = ...;
etc? (In 2024 so far, this is an error). There are roughly three options:
x: &T
here.x: &&T
here.Points to consider:
&mut
to &
:ref x
is always allowed and gives a &T
;ref
always adds a layer of reference", and conversely "removing ref
always removes a layer of reference".I propose option 2.
Take the following:
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:
Some points to consider:
ref x
, adding a ref
would suffice to fix the borrow errors in question instead;let &[x]: &[&mut T]
is still an error;let &[&mut x]: &&mut [T]
becomes a type error;
let &&mut [x]: &&mut [T]
is still allowed, which may feel inconsistent;&
.