-
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
RFC: mut (x, y, ..)
and mut [x, y, ..]
pattern shorthand
#2401
Conversation
While this is (mostly) not a problem any more due to the default match bindings, I think that these rules should apply to |
@clarcharr So the reason why I didn't add that was the default match bindings you mention. However, I don't mind it personally, and if there is consensus within the lang team and within the community, I will certainly add it. |
Hmm, I'm torn about this statement
On the one hand I like the restriction to "special" types (tuples & arrays), but at the same time patterns nest, so you can still have |
text/0000-mut-pattern-shorthand.md
Outdated
| first binding | ||
``` | ||
|
||
## Reversed polarity with `immut` |
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.
We could use const
(as in *mut T
vs *const T
) instead of introducing yet another keyword. (I'm ๐ to allowing reversed polarity anyway).
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.
So the point of this section is to say that we should not introduce immut
;) So I agree entirely with you here that we shouldn't do it.
|
||
None as of yet. | ||
|
||
# Future work |
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.
We have recently implemented #554 (rust-lang/rust#48500). Would mut
next to a parenthesis pattern be allowed?
match 5 {
mut (x) => {}
}
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.
So looking as parser-lalr.y
, we have:
pat
: ...
| '(' pat_tup ')' { $$ = mk_node("PatTup", 1, $2); }
which I extended with MUT '(' pat_tup ')'
Then, pat_tup
eventually leads to a production MUT '(' pat ')'
so it seems that per the grammar I've included in the reference, mut (x) => {}
would be permitted.
Is that a good thing or a bad thing..? I don't know. It seems simpler to permit mut (x)
even if it is not the recommended style.
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.
@Centril the *.y
file is not up-to-date though, and (x)
isn't a tuple (the 1-tuple syntax is ($pat,)
).
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.
Ah damn :/ so my answer can't be reliable here if the *.y
file is not up to date.
If we want to do the broader change per @scottmcm's comment above, it makes sense to also permit mut (x)
. What is your view?
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.
@Centril I think if parenthesis is allowed, then almost all patterns should support leading mut (except the ambiguous-to-parse stuff like x @ pat
, x | y
and x..=y
) because literally any pattern can appear inside (โฆ)
.
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.
Hmm, @kennytm's comment pushes me back down to -0 on this, since it reminded me that the proposed grammar will allow all kinds of silliness like mut (1...10, 20...30)
. That's certainly not a blocking issue -- I'm confident it could be linted without too much trouble -- but does emphasize that the current "mut is part of a binding" rule is substantially simpler.
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.
So thinking about what such a lint could be, an analysis that checks the pattern (1..=10, 20..=30)
for bindings would find that there are none, and so a mut
in front of it would apply to zero bindings, which would be the lint condition. That's not so complex?
The current rule is for sure simpler tho =)
Yeah; I'm torn about this as well, and you make a fine argument. Trying to restrict patterns, like the one you've mentioned, which we might consider unidiomatic will probably just cause hazards for the future. It might be easier wrt. complexity to just do the broader change. I look forward to the community and the lang team's guidance to make me untorn =P |
text/0000-mut-pattern-shorthand.md
Outdated
also has redundant `mut`s either outside the tuple or inside the tuple. | ||
When such a redundancy happens, the compiler should unconditionally warn the | ||
user. Furthermore, when `let mut (x, y)` results in partial unused `mut`ability | ||
then the compiler should also warn. |
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.
nitpick: I don't think "unconditionally" is appropriate; this can just be an extension of unused_mut
(or maybe a new lint, but distinguishing seems unnecessary to me).
So you get something suppressible; maybe
warning: variable is already mutable
--> src/main.rs:2:9
|
2 | let mut (mut x, y) = (4, 5;
| ----^
| |
| help: remove this `mut`
|
= note: #[warn(unused_mut)] on by default
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.
Yeah; I think your comment reflects what I actually meant ;)
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.
This further reduces the usefulness to macros, though I guess the double-mut warnings could be turned off inside macros.
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.
@durka The standard approach is that you can disable lints with #[allow(..)]
, and that should be doable inside macros as usual.
This doesn't look common enough to me to meet the threshold on a language addition. But we can actually estimate the effect on the crates.io ecosystem by writing a deny-by-default lint and running it through crater. |
By the way, how mut (ref x, ref mut y)
<=>
(mut ref x, mut ref mut y)
<=>
(ref x, ref mut y)
let mut x = x;
let mut y = y; Currently we don't have syntax to do this directly in the pattern. |
Sounds like a nice idea. Would you mind taking a crack at it?
I think the interpretation you described is reasonable and is in line with the RFC as currently specified. |
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 makes some declarations shorter (though maybe harder to read, IMO), but it feels strange to me because it adds a special case to the pattern syntax, which right now is a nice collection of small composable parts. Paradoxically, I think the radical option (mut
affects all idents after it, even in destructured structs) would mitigate that objection, even though it allows hairier patterns.
Another effect of reduced repetition is that it now becomes easy to create | ||
a macro that introduces mutable bindings everywhere (for tuples and arrays) | ||
by simply adding a single `mut`. This way, you can take a `pat` fragment and | ||
prefix it with `mut` to make the entire pattern introduce mutable bindings. |
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.
Hi, I annoyingly pop up when people tout spurious and/or exaggerated macro ergonomics as advantages of RFCs.
If this is to actually be supported, it is another feature request (probably a good one, though!). Similar code doesn't currently work because you can't modify an already-parsed nonterminal. That is, this doesn't compile:
macro_rules! m {
($p:pat, $e:expr) => {
let mut $p = $expr; // theory: 'mut' + pat = pat
}
}
m!(x, 42); //~ERROR expected identifier
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 seems to me that the problem here is that mut $pat
is not valid and mut
expects an identifier, i.e: mut $ident
. Indeed, the following snippet is valid:
macro_rules! m { ($p:ident, $e:expr) => { let mut $p = $e; } }
fn main() { m!(x, 42); }
Any suggestions on how to modify the reference to actually support this case?
PS: ping me on IRC for more sync discussions :)
text/0000-mut-pattern-shorthand.md
Outdated
also has redundant `mut`s either outside the tuple or inside the tuple. | ||
When such a redundancy happens, the compiler should unconditionally warn the | ||
user. Furthermore, when `let mut (x, y)` results in partial unused `mut`ability | ||
then the compiler should also warn. |
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.
This further reduces the usefulness to macros, though I guess the double-mut warnings could be turned off inside macros.
For others, readability will be increased thanks to reduced noise. | ||
The ability to edit can also be increased when you only need to add `mut` after | ||
`let` to introduce mutable bindings. A mitigating factor to reduced readability | ||
for some is the fact that using this new syntax is entirely optional. |
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.
"The new syntax is optional" is never a valid argument. If you don't use it, someone else will, and code is read more often than written.
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 won't agree to such a sweeping statement - to me there's more nuance. I believe it's a valid argument as a mitigating factor. Yes, code is read more often than written in general, but that also depends on the project. Sometimes code is written and read exactly as much (because you wrote the code for a hobby project, wrote tests for it, and now you can forget about the implementation).
To me, the locality problem was not a particularly serious drawback to begin with; I mention it primarily for the purposes of being exhaustive. On balance, I believe the new introduced syntax improves readability.
Of course, this way of writing may not be to everyone's liking. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation |
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.
One thing I'm worried about is people assuming that let mut
is a unit, rather than let $pat
. This probably already happens a ton, since single ident bindings are the most common. But maybe it should be called out in the guide somehow.
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 called it out now at the end of the first subsection. Check it out / Propose improvements?
and pattern matched on instead of `mut` being prefixed on a lot or all bindings. | ||
The single `mut` introduced as in `let mut (x, (y, z)) = ..` should still be | ||
sufficiently close in visual scope to not require holding more things in short | ||
term memory. |
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'm not sure about this. Patterns can get pretty long. When I wrote a macro that recursed through patterns, if this feature were included there's an extra bit of state to track, whereas in ye olde patterns, once you found an ident, you could look at the token right before it and know what it was. I guess default binding modes already changed this though. And the fact that it's one-way (mutability only goes on, and can't be reversed deeper in the pattern) reduces the added cognitive overhead somewhat.
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.
Often nothing can replace exercising good judgement.
My personal taste is that the method and the specific match arm is significantly too long and should be split up (but I'm from the radical school of 1-10 line functions in Haskell so I'm a bit special .. ). Even so, IMO, the pattern you reference is still close enough in visual scope to not be a problem.
Just a heads up, we should probably use permalinks to code instead, so the links remain correct even after the code changes in later commits, since the RFC may be longer lived than the code, especially if this RFC is approved and these places are updated to make use of the proposed syntax:
|
@blaenk Fixed; Thanks! |
This grep catches the common case for this feature. Some matches are false positives but still there seems to be hundreds of lines of code that could really use this, only in the ~600 crates searched. |
What about structure destructuring: let mut { foo, bar } = value; |
@sergeysova That pattern |
Can macros expand to patterns? let (mut_all!(a, b, c)) = (1, 2, 3);
let Foo { mut_all!(a, b, c) } = Foo { a: 1, b: 2, c: 3 }; If so, macros have their semantics resolved already, so we should use macros to keep the language simpler. |
Update: |
The more I look at this, the more it feels like an unusual special case that requires a double-take when reading code. When you already have a tuple or slice, it doesn't feel that onerous to have to write And I don't think we should encourage the use of tuples to cram multiple |
I actually already use |
This seems a bit fraught to me. If it were done then I'd vote to do it very conservatively, and make sure it's as uniform and regular as we can make it--nobody likes special cases. |
Oppose. Needless churn. As the RFC itself asks: "Does the value of this outweigh the cost of having multiple ways to do it". IMO the answer is no. |
This is a relatively small change to the surface area of the language, but it's still a change, and one that seems inconsistent with where @rfcbot close |
Team member @joshtriplett has proposed to close this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
๐ This is now entering its final comment period, as per the review above. ๐ |
The final comment period, with a disposition to close, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC is now closed. |
An update... With rust-lang/rust#63945 the compiler now has appropriate parser recovery: fn foo() {
let mut [x, y] = 0;
let mut (x, y) = 0;
} results in: error: `mut` must be attached to each individual binding
--> src/lib.rs:2:9
|
2 | let mut [x, y] = 0;
| ^^^^^^^^^^ help: add `mut` to each binding: `[mut x, mut y]`
|
= note: `mut` may be followed by `variable` and `variable @ pattern`
error: `mut` must be attached to each individual binding
--> src/lib.rs:4:9
|
4 | let mut (x, y) = 0;
| ^^^^^^^^^^ help: add `mut` to each binding: `(mut x, mut y)`
|
= note: `mut` may be followed by `variable` and `variable @ pattern`
error[E0529]: expected an array or slice, found `{integer}`
--> src/lib.rs:2:9
|
2 | let mut [x, y] = 0;
| ^^^^^^^^^^ pattern cannot match with input type `{integer}`
error[E0308]: mismatched types
--> src/lib.rs:4:9
|
4 | let mut (x, y) = 0;
| ^^^^^^^^^^ expected integer, found tuple
|
= note: expected type `{integer}`
found type `(_, _)`
error: aborting due to 4 previous errors |
๐ผ๏ธ Rendered
๐ Summary
This RFC aims to improve ergonomics by allowing you to write:
๐ Thanks
To @scottmcm, @varkor and others for their review and interesting discussions.