Skip to content

Latest commit

 

History

History
272 lines (213 loc) · 9.63 KB

3410-explicit-move-binding-mode.md

File metadata and controls

272 lines (213 loc) · 9.63 KB

Summary

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).

Motivation

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.

Guide-level explanation

Expert explanation

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.

Beginner explanation

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`

Reference-level explanation

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.

Drawbacks

  • It can be argued that use of the move keyword should be replaced with use of the ref 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.

Rationale and alternatives

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.

Prior art

None that I know of. Other languages don’t have match ergonomics as far as I know.

Unresolved questions

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 for move mut:

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`
  |

Unnecessary 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)

Unnecessary ref:

  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.

Future possibilities

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.