-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
schuelermine
wants to merge
35
commits into
rust-lang:master
Choose a base branch
from
schuelermine:explicit-move-binding-mode
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
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 72c9b30
explicit-move-binding-mode: move RFC to match PR number
schuelermine b5c800f
explicit-move-binding-mode: move `move mut` into unresolved questions
schuelermine 94a3482
explicit-move-binding-mode: clarify
schuelermine 2926ee1
explicit-move-binding-mode: fix header
schuelermine 4544090
explicit-move-binding-mode: fix mistake in table description
schuelermine cf0b915
explicit-move-binding-mode: mark example warning code blocks as langu…
schuelermine 38aee06
explicit-move-binding-mode: fix error example
schuelermine eb58dc6
explicit-move-binding-mode: clarify lint and provide examples for alt…
schuelermine c6a79e0
explicit-move-binding-mode: add newline
schuelermine f0c81b5
explicit-move-binding-mode: elaborate on alternatives & fix future po…
schuelermine 45c34d8
explicit-move-binding-mode: fix two mistakes
schuelermine 5d2a1d3
explicit-move-binding-mode: +slightly
schuelermine 8df5b7d
explicit-move-binding-mode: give alternate keyword possibilities
schuelermine 4efd856
explicit-move-binding-mode: better explaination for why this is alrea…
schuelermine 2a345b3
explicit-move-binding-mode: fix syntax highlighting
schuelermine 5e4067b
explicit-move-binding-mode: remove superfluous “then”
schuelermine dd51981
explicit-move-binding-mode: make wording clearer
schuelermine 9d67c67
explicit-move-binding-mode: remove a “the”
schuelermine 7166c59
explicit-move-binding-mode: add more warning ideas & small updates
schuelermine 17522df
explicit-move-binding-mode: fix & explain warning example
schuelermine 4b42458
explicit-move-binding-mode: various small adjustments
schuelermine aa1143d
explicit-move-binding-mode: explain that the move is part of a copy
schuelermine 7dfb3e9
explicit-move-binding-mode: clarify
schuelermine a9cb256
explicit-move-binding-mode: Revert "explicit-move-binding-mode: expla…
schuelermine ac8cf43
explicit-move-binding-mode: add drawbacks
schuelermine c014c2d
explicit-move-binding-mode: small adjustments
schuelermine 0fe609d
explicit-move-binding-mode: add future possibility
schuelermine d910629
explicit-move-binding-mode: slight adjustment of the text
schuelermine bf16a67
explicit-move-binding-mode: refined symbols
schuelermine cf38c64
explicit-move-binding-mode: note the other definition of reference pa…
schuelermine e9b7bbf
explicit-move-binding-mode: list some keyword ideas
schuelermine 06a7313
explicit-move-binding-mode: add divider
schuelermine 1ccc69e
explicit-move-binding-mode: add copy as a possible keyword
schuelermine dbd2370
explicit-move-binding-mode: change ditto symbol to "
schuelermine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. | ||
|
||
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). |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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 thatconst
is an alternative. I’m not sure what you’re trying to say—do you thinkconst
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.There was a problem hiding this comment.
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
(asnot_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 thanmove
:But I don't know if Keyword
new
is possible to make soft.There was a problem hiding this comment.
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
!There was a problem hiding this comment.
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.