Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit move binding mode #3410

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Changes from 27 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f4bfed3
explicit-move-binding-mode: write RFC
schuelermine Apr 7, 2023
72c9b30
explicit-move-binding-mode: move RFC to match PR number
schuelermine Apr 7, 2023
b5c800f
explicit-move-binding-mode: move `move mut` into unresolved questions
schuelermine Apr 7, 2023
94a3482
explicit-move-binding-mode: clarify
schuelermine Apr 7, 2023
2926ee1
explicit-move-binding-mode: fix header
schuelermine Apr 7, 2023
4544090
explicit-move-binding-mode: fix mistake in table description
schuelermine Apr 7, 2023
cf0b915
explicit-move-binding-mode: mark example warning code blocks as langu…
schuelermine Apr 7, 2023
38aee06
explicit-move-binding-mode: fix error example
schuelermine Apr 7, 2023
eb58dc6
explicit-move-binding-mode: clarify lint and provide examples for alt…
schuelermine Apr 8, 2023
c6a79e0
explicit-move-binding-mode: add newline
schuelermine Apr 8, 2023
f0c81b5
explicit-move-binding-mode: elaborate on alternatives & fix future po…
schuelermine Apr 8, 2023
45c34d8
explicit-move-binding-mode: fix two mistakes
schuelermine Apr 8, 2023
5d2a1d3
explicit-move-binding-mode: +slightly
schuelermine Apr 8, 2023
8df5b7d
explicit-move-binding-mode: give alternate keyword possibilities
schuelermine Apr 8, 2023
4efd856
explicit-move-binding-mode: better explaination for why this is alrea…
schuelermine Apr 8, 2023
2a345b3
explicit-move-binding-mode: fix syntax highlighting
schuelermine Apr 8, 2023
5e4067b
explicit-move-binding-mode: remove superfluous “then”
schuelermine Apr 8, 2023
dd51981
explicit-move-binding-mode: make wording clearer
schuelermine Apr 9, 2023
9d67c67
explicit-move-binding-mode: remove a “the”
schuelermine Apr 9, 2023
7166c59
explicit-move-binding-mode: add more warning ideas & small updates
schuelermine Apr 9, 2023
17522df
explicit-move-binding-mode: fix & explain warning example
schuelermine Apr 9, 2023
4b42458
explicit-move-binding-mode: various small adjustments
schuelermine Apr 10, 2023
aa1143d
explicit-move-binding-mode: explain that the move is part of a copy
schuelermine Apr 10, 2023
7dfb3e9
explicit-move-binding-mode: clarify
schuelermine Apr 10, 2023
a9cb256
explicit-move-binding-mode: Revert "explicit-move-binding-mode: expla…
schuelermine Apr 10, 2023
ac8cf43
explicit-move-binding-mode: add drawbacks
schuelermine Apr 10, 2023
c014c2d
explicit-move-binding-mode: small adjustments
schuelermine Apr 10, 2023
0fe609d
explicit-move-binding-mode: add future possibility
schuelermine Apr 10, 2023
d910629
explicit-move-binding-mode: slight adjustment of the text
schuelermine Apr 11, 2023
bf16a67
explicit-move-binding-mode: refined symbols
schuelermine Apr 11, 2023
cf38c64
explicit-move-binding-mode: note the other definition of reference pa…
schuelermine Apr 12, 2023
e9b7bbf
explicit-move-binding-mode: list some keyword ideas
schuelermine Apr 12, 2023
06a7313
explicit-move-binding-mode: add divider
schuelermine Apr 12, 2023
1ccc69e
explicit-move-binding-mode: add copy as a possible keyword
schuelermine Apr 14, 2023
dbd2370
explicit-move-binding-mode: change ditto symbol to "
schuelermine May 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 265 additions & 0 deletions text/3410-explicit-move-binding-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
- Feature Name: explicit_move_binding_mode
- Start Date: 2023-04-07
- RFC PR: [rust-lang/rfcs#3410](https://github.com/rust-lang/rfcs/pull/3410)

# Summary
[summary]: #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
[motivation]: #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
[guide-level-explanation]: #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`.

```rust
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 (`&<pattern>` or similar) is matched against a
value that is a reference (`&<type>` or `&mut <type>`), the value is automatically dereferenced,
as though the pattern had been written `&<pattern>`, and the default
binding mode is set to `ref` or `ref mut`, depending on if the reference is mutable.
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:

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

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

```rust
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
[reference-level-explanation]: #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
[drawbacks]: #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
[rationale-and-alternatives]: #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. If this path is desired I suggest `bind`.
Copy link

@VitWW VitWW Apr 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is written desired parameter property currently in sharing part of deconstruct pattern.
Attribute ref mean we wish parameter to be a reference or same referential as deconstructed variable if attribute is off.
Attribute mut mean we wish parameter to be a mutable one or same changeable as deconstructed variable if attribute is off.

Possible alternative attribute const mean we wish parameter to be a immutable one or same changeable as deconstructed variable if attribute is off.

But proposed attribute move mean we wish deconstruct value to be moved or same changeable as deconstructed variable if attribute is off.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const for a binding implies that it is known at compile time, which is not the case here. I also already noted that const is an alternative. I’m not sure what you’re trying to say—do you think const is a better option? I also do not understand your reasoning. We are deconstructing and moving a value. Additionally your language is hard to understand.

Copy link

@VitWW VitWW Apr 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@schuelermine thank you for adding!
Both move & const(as not_mut) have same strength level of expressiveness.
Simple explanation: let Some(const x) = &mut y; we read as "some of constant(not mutable) variable X deconstructed from mutable reference Y"
But let Some(move x) = &mut y; we read as "some of moved from variable Y into variable X deconstructed from mutable reference Y". We don't move any X, but Y.

Maybe a word new/fresh/free has much better meaning than move:

let Some(free x)  = &mut y;
let Some(new x)   = &mut y;
let Some(fresh x) = &mut y;

But I don't know if Keyword new is possible to make soft.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@schuelermine Finally I found the right word for move-meaning - value !

let Some(value x) = &mut y;
let Some(ref x)   = &mut y;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve added a list of possible keywords including your ideas.


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:

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

```rust
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
[prior-art]: #prior-art

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

# Unresolved questions
[unresolved-questions]: #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`:

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

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

```none
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
[future-possibilities]: #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).