- Feature Name: explicit_move_binding_mode
- Start Date: 2023-04-07
- RFC PR: rust-lang/rfcs#3410
Enable the use of the move
keyword to explicitly specify the moving binding
mode in patterns. This allows users to opt out of match ergonomics for some but not all bindings.
Warn about unnecessary keywords that specify binding mode (called “specifiers” in this document).
Currently, there are multiple binding modes in patterns, but only some are explicitly specifiable.
This is an obvious inconsistency, as match ergonomics permit changing the
default binding mode of a pattern. Changing it back is only natural, as changing it
to the non-default mutable move is possible—that is, writing mut
overrides match ergonomics
and performs a move after the dereference, although the resulting binding is mutable.
Specifically, when most bindings of a large pattern should be of one binding mode,
but some should be moves, it is inconvenient to forgo match ergonomics entirely
and repeatedly use ref
or ref mut
specifiers.
The move
keyword resets the binding mode for an individual identifier pattern
to the moving mode. The meaning of mut
remains the same.
The matching still dereferences by match ergonomics rules.
When deconstructing a value, it is sometimes desirable to get a reference to the
element bound to a variable instead of moving that value into the variable. To do
this, you can set the binding mode of an identifier pattern by prefixing it with
ref
, ref mut
(or move
, but this is the default). You can also use this syntax
to make a plain move mutable, by prefixing with just mut
.
let mut possibly_x: Option<i32> = Some(37);
if let Some(ref mut x) = possibly_x {
*x += 2;
} // Here we use `ref mut` to get a mutable
// reference to the value contained in `possibly_x`
match possibly_x {
None => {
println!("No value found!");
println!("Can’t work with non-existant value.")
}
Some(mut x) => {
println!("The value is {x}.");
x += 2;
println!("That value plus two is {x}.");
} // Here we use `mut` to mark the binding
// as mutable, allowing us to modify it
// Note that this does not change the value
// inside `possibly_x`, as we did not use `ref mut`
}
Match ergonomics allow you to more easily get references to bindings in patterns.
When a pattern that is not a reference pattern or a pattern that can match anything (_
or identifier patterns)
is matched against a value that is a reference, the value is automatically dereferenced,
as though the pattern had been written &<pattern>
or &mut <pattern>
, and the default
binding mode is set to ref
or ref mut
.
All identifier patterns (x
and the like) that don’t have an explicit binding mode
instead bind with binding mode ref
or ref mut
.
This means that let (x, y) = &a
is the same as let &(ref x, ref y) = &a
.
We can rewrite the above example as follows:
let mut possibly_x: Option<i32> = Some(37);
if let Some(x) = &mut possibly_x {
*x += 2
} // Here, `x` has the type `&mut i32`
You can opt out of this behaviour for individual identifier patterns by prefixing
them with move
or mut
. Note that you cannot create a mutable reference from an
immutable one, and this is not what mut
does—it sets the binding mode to a mutable move.
let mut x_and_y: (i32, i32) = (25, -4);
let (x, mut y) = &mut x_and_y;
// The type of `x` is `&mut i32` and
// the type of `y` is `i32` (and the binding is mutable)
*x += 2; // `x_and_y` is modified
y += 2; // `x_and_y` is not modified
let (move x, y) = &x_and_y;
// The type of `x` is `i32` and
// the type of `y` is `&i32`
You can also switch to an immutable reference binding mode by using the ref
keyword.
let mut x_y_z: (i32, i32, i32) = (400, 1, -99);
let (x, ref y, move z) = &mut x_y_z;
// The type of `x` is `&mut i32`,
// the type of `y` is `&i32` and
// the type of `z` is `i32`
The syntax for IdentifierPattern is updated as follows:
IdentifierPattern:
(
ref
|move
)?mut
? IDENTIFIER (@
PatternNoTopAlt)?
The binding mode of a binding depends on the default mode and
the binding mode specifier (mut
, move
, ref
, or ref mut
)
and is described by the following table. If the entry into the table is
followed by an exclamation mark in parentheses (“(!)”), a lint is triggered.
The symbol “"” indicates that the entry is the same as the entry to the left,
excluding whether it triggers the lint.
↓specifier | →default = move | reference | mutable reference |
---|---|---|---|
mut |
move mutable | " | " |
ref mut |
mutable reference | " | " (!) |
ref |
reference | " (!) | " |
move |
move (!) | " | " |
none | move | reference | mutable reference |
The lint is controlled by unnecessary_binding_mode
. It is warn-by-default.
- It can be argued that use of the
move
keyword should be replaced with use of theref
keyword and not using match ergonomics at all. - This further entrenches the unintuitive fact that
mut
sets the binding mode to a mutable move and disables referencing. - This means that either the grammar gets more complicated or an additional sequence is allowed by the grammar but disallowed by the compiler.
- The
move
keyword can be confusing here because a copy may happen instead.
I believe the move
keyword is an excellent candidate for syntax here,
as it already exists and is also used by the match ergonomics RFC.
Alternative keywords would be const
or let
.
Alternatively, a new keyword could be added, although this would need to be
a soft keyword or happen over an edition boundary.
Possible keywords are copy
, bind
, value
, free
, new
, just
, get
, fresh
, set
, var
.
An alternative to this proposal is to update match ergonomics such that a non-reference pattern matched against a reference does not update the binding mode, but instead matches the subpatterns against borrowed subvalues. This would allow writing this:
let x_and_y: (i32, i32) = (-9, 2);
let (x, &y) = &x_and_y;
// `x` is of type `&i32`, while
// `y` is of type `i32`
Then, the mut
keyword could be used to make the binding itself
mutable instead of the reference the binding binds.
let mut a = 3;
let mut x_and_y: (i32, i32) = (77, 0);
let (mut x, y) = &mut x_and_y;
*x += 2; // `x_and_y` is modified
x = &mut a;
*x += 2; // `a` is modified
Note that this proposal likely requires an edition boundary unless complicated backwards-compatibility measures are employed.
A similar possibility for the current proposal: The combination mut ref
could
be added distinctly from ref mut
to make the binding mutable, not the reference.
None that I know of. Other languages don’t have match ergonomics as far as I know.
How should the combination move mut
be handled? Should it generate an error or be warned against, working as if it was bare mut
?
I believe having the combination of move
vs ref
and mut
vs nothing be as simple as concatenation
could be useful for macros.
It is also possible to change the preferred way of indicating a mutable move to move mut
and warn against plain mut
instead, as plain mut
switching to moves is unintuitive.
What should the warnings/errors look like? Here are some ideas:
error: move semantics can't be specified here, use bare `mut` instead
--> src/main.rs:4:10
|
4 | (move mut x, y, z) => {
| ----
| |
| help: remove this `move`
|
error: unnecessary binding mode specifier
--> src/main.rs:4:10
|
4 | (move x, y, z) => {
| ^^^^
| |
| `move` not required here because it's implied
|
= note: `#[warn(unnecessary_binding_mode)]` on by default
(inspired by E0449)
warning: ref semantics don't need to be specified here because you're matching against a reference
--> src/main.rs:4:10
|
3 | match &(a, b, c) {
| ^---------
| |
| reference originates here
|
4 | (ref x, y, z) => {
| ---
| |
| help: remove this `ref`
|
= note: `#[warn(unnecessary_binding_mode)]` on by default
Note that the author is not intimately familiar with the style of rustc
error messages, so these likely need adjustment.
It is somewhat unintuitive that the mut
specifier sets the binding mode to a mutable move.
It would be possible to update match ergonomics in a future edition of Rust to have specifiers
modify the binding mode instead of setting them, or require move mut
instead of
just mut
for mutable moves (see also the alternatives section).
It would also be possible to demote mut
from the status of binding mode specifier and instead
have it be completely orthogonal to binding mode.