- Feature Name: pattern-binding-modes
- Start Date: 2016-08-12
- RFC PR: [rust-lang/rfcs#2005](https://github.com/rust-lang/rfcs/pull/2005)
- Rust Issue: [rust-lang/rust#42640](https://github.com/rust-lang/rust/issues/42640)
# Summary
[summary]: #summary
Better ergonomics for pattern-matching on references.
Currently, matching on references requires a bit of a dance using
`ref` and `&` patterns:
```rust
let x: &Option<_> = &Some(0);
match x {
&Some(ref y) => { ... },
&None => { ... },
}
// or using `*`:
match *x {
Some(ref x) => { ... },
None => { ... },
}
```
After this RFC, the above form still works, but now we also allow a simpler form:
```rust
let x: &Option<_> = &Some(0);
match x {
Some(y) => { ... }, // `y` is a reference to `0`
None => { ... },
}
```
This is accomplished through automatic dereferencing and the introduction of
default binding modes.
# Motivation
[motivation]: #motivation
Rust is usually strict when distinguishing between value and reference types. In
particular, distinguishing borrowed and owned data. However, there is often a
trade-off between [explicit-ness and ergonomics](https://blog.rust-lang.org/2017/03/02/lang-ergonomics.html),
and Rust errs on the side of ergonomics in some carefully selected places.
Notably when using the dot operator to call methods and access fields, and when
declaring closures.
The match expression is an extremely common expression and arguably, the most
important control flow mechanism in Rust. Borrowed data is probably the most
common form in the language. However, using match expressions and borrowed data
together can be frustrating: getting the correct combination of `*`, `&`, and
`ref` to satisfy the type and borrow checkers is a common problem, and one which
is often encountered early by Rust beginners. It is especially frustrating since
it seems that the compiler can guess what is needed but gives you error messages
instead of helping.
For example, consider the following program:
```rust
enum E { Foo(...), Bar }
fn f(e: &E) {
match e { ... }
}
```
It is clear what we want to do here - we want to check which variant `e` is a
reference to. Annoyingly, we have two valid choices:
```rust
match e {
&E::Foo(...) => { ... }
&E::Bar => { ... }
}
```
and
```rust
match *e {
E::Foo(...) => { ... }
E::Bar => { ... }
}
```
The former is more obvious, but requires more noisey syntax (an `&` on every
arm). The latter can appear a bit magical to newcomers - the type checker treats
`*e` as a value, but the borrow checker treats the data as borrowed for the
duration of the match. It also does not work with nested types, `match (*e,)
...` for example is not allowed.
In either case if we further bind variables, we must ensure that we do not
attempt to move data, e.g.,
```rust
match *e {
E::Foo(x) => { ... }
E::Bar => { ... }
}
```
If the type of `x` does not have the `Copy` bound, then this will give a borrow
check error. We must use the `ref` keyword to take a reference: `E::Foo(ref x)`
(or `&E::Foo(ref x)` if we match `e` rather than `*e`).
The `ref` keyword is a pain for Rust beginners, and a bit of a wart for everyone
else. It violates the rule of patterns matching declarations, it is not found
anywhere outside of patterns, and it is often confused with `&`. (See for
example, https://github.com/rust-lang/rust-by-example/issues/390).
Match expressions are an area where programmers often end up playing 'type
Tetris': adding operators until the compiler stops complaining, without
understanding the underlying issues. This serves little benefit - we can make
match expressions much more ergonomic without sacrificing safety or readability.
Match ergonomics has been highlighted as an area for improvement in 2017:
[internals thread](https://internals.rust-lang.org/t/roadmap-2017-productivity-learning-curve-and-expressiveness/4097)
and [Rustconf keynote](https://www.youtube.com/watch?v=pTQxHIzGqFI&list=PLE7tQUdRKcybLShxegjn0xyTTDJeYwEkI&index=1).
# Detailed design
[design]: #detailed-design
This RFC is a refinement of
[the match ergonomics RFC](https://github.com/rust-lang/rfcs/pull/1944). Rather
than using auto-deref and auto-referencing, this RFC introduces _default binding
modes_ used when a reference value is matched by a non-reference pattern.
In other words, we allow auto-dereferencing values during pattern-matching.
When an auto-dereference occurs, the compiler will automatically treat the inner
bindings as `ref` or `ref mut` bindings.
Example:
```rust
let x = Some(3);
let y: &Option = &x;
match y {
Some(a) => {
// `y` is dereferenced, and `a` is bound like `ref a`.
}
None => {}
}
```
Note that this RFC applies to all instances of pattern-matching, not just
`match` expressions:
```rust
struct Foo(i32);
let foo = Foo(6);
let foo_ref = &foo;
// `foo_ref` is dereferenced, and `x` is bound like `ref x`.
let Foo(x) = foo_ref;
```
## Definitions
A reference pattern is any pattern which can match a reference without
coercion. Reference patterns include bindings, wildcards (`_`),
`const`s of reference types, and patterns beginning with `&` or `&mut`. All
other patterns are _non-reference patterns_.
_Default binding mode_: this mode, either `move`, `ref`, or `ref mut`, is used
to determine how to bind new pattern variables.
When the compiler sees a variable binding not explicitly marked
`ref`, `ref mut`, or `mut`, it uses the _default binding mode_
to determine how the variable should be bound.
Currently, the _default binding mode_ is always `move`.
Under this RFC, matching a reference with a _non-reference pattern_, would shift
the default binding mode to `ref` or `ref mut`.
## Binding mode rules
The _default binding mode_ starts out as `move`. When matching a pattern, the
compiler starts from the outside of the pattern and works inwards.
Each time a reference is matched using a _non-reference pattern_,
it will automatically dereference the value and update the default binding mode:
1. If the reference encountered is `&val`, set the default binding mode to `ref`.
2. If the reference encountered is `&mut val`: if the current default
binding mode is `ref`, it should remain `ref`. Otherwise, set the current binding
mode to `ref mut`.
If the automatically dereferenced value is still a reference, it is dereferenced
and this process repeats.
```
Start
|
v
+-----------------------+
| Default Binding Mode: |
| move |
+-----------------------+
/ \
Encountered / \ Encountered
&mut val / \ &val
v v
+-----------------------+ +-----------------------+
| Default Binding Mode: | | Default Binding Mode: |
| ref mut | | ref |
+-----------------------+ +-----------------------+
----->
Encountered
&val
```
Note that there is no exit from the `ref` binding mode. This is because an
`&mut` inside of a `&` is still a shared reference, and thus cannot be used to
mutate the underlying value.
Also note that no transitions are taken when using an explicit `ref` or
`ref mut` binding. The _default binding mode_ only changes when matching a
reference with a non-reference pattern.
The above rules and the examples that follow are drawn from @nikomatsakis's
[comment proposing this design](https://github.com/rust-lang/rfcs/pull/1944#issuecomment-296133645).
## Examples
No new behavior:
```rust
match &Some(3) {
p => {
// `p` is a variable binding. Hence, this is **not** a ref-defaulting
// match, and `p` is bound with `move` semantics
// (and has type `&Option`).
},
}
```
One match arm with new behavior:
```rust
match &Some(3) {
Some(p) => {
// This pattern is not a `const` reference, `_`, or `&`-pattern,
// so this is a "non-reference pattern."
// We dereference the `&` and shift the
// default binding mode to `ref`. `p` is read as `ref p` and given
// type `&i32`.
},
x => {
// In this arm, we are still in `move`-mode by default, so `x` has type
// `&Option`
},
}
// Desugared:
match &Some(3) {
&Some(ref p) => {
...
},
x => {
...
},
}
```
`match` with "or" (`|`) patterns:
```rust
let x = &Some((3, 3));
match x {
// Here, each of the patterns are treated independently
Some((x, 3)) | &Some((ref x, 5)) => { ... }
_ => { ... }
}
// Desugared:
let x = &Some((3, 3));
match x {
&Some((ref x, 3)) | &Some((ref x, 5)) => { ... }
None => { ... }
}
```
Multiple nested patterns with new and old behavior, respectively:
```rust
match (&Some(5), &Some(6)) {
(Some(a), &Some(mut b)) => {
// Here, the `a` will be `&i32`, because in the first half of the tuple
// we hit a non-reference pattern and shift into `ref` mode.
//
// In the second half of the tuple there's no non-reference pattern,
// so `b` will be `i32` (bound with `move` mode). Moreover, `b` is
// mutable.
},
_ => { ... }
}
// Desugared:
match (&Some(5), &Some(6)) {
(&Some(ref a), &Some(mut b)) => {
...
},
_ => { ... },
}
```
Example with multiple dereferences:
```rust
let x = (1, &Some(5));
let y = &Some(x);
match y {
Some((a, Some(b))) => { ... }
_ => { ... }
}
// Desugared:
let x = (1, &Some(5));
let y = &Some(x);
match y {
&Some((ref a, &Some(ref b))) => { ... }
_ => { ... }
}
```
Example with nested references:
```rust
let x = &Some(5);
let y = &x;
match y {
Some(z) => { ... }
_ => { ... }
}
// Desugared:
let x = &Some(5);
let y = &x;
match y {
&&Some(ref z) => { ... }
_ => { ... }
}
```
Example of new mutable reference behavior:
```rust
let mut x = Some(5);
match &mut x {
Some(y) => {
// `y` is an `&mut` reference here, equivalent to `ref mut` before
},
None => { ... },
}
// Desugared:
match &mut x {
&mut Some(ref mut y) => {
...
},
&mut None => { ... },
}
```
Example using `let`:
```rust
struct Foo(i32);
// Note that these rules apply to any pattern matching
// whether it be in a `match` or a `let`.
// For example, `x` here is a `ref` binding:
let Foo(x) = &Foo(3);
// Desugared:
let &Foo(ref x) = &Foo(3);
```
## Backwards compatibility
In order to guarantee backwards-compatibility, this proposal only modifies
pattern-matching when a reference is matched with a non-reference pattern,
which is an error today.
This reasoning requires that the compiler knows if the type being matched is a
reference, which isn't always true for inference variables.
If the type being matched may
or may not be a reference _and_ it is being matched by a _non-reference
pattern_, then the compiler will default to assuming that it is not a
reference, in which case the binding mode will default to `move` and it will
behave exactly as it does today.
Example:
```rust
let x = vec![];
match x[0] { // This will panic, but that doesn't matter for this example
// When matching here, we don't know whether `x[0]` is `Option<_>` or
// `&Option<_>`. `Some(y)` is a non-reference pattern, so we assume that
// `x[0]` is not a reference
Some(y) => {
// Since we know `Vec::contains` takes `&T`, `x` must be of type
// `Vec