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

RFC: mut (x, y, ..) and mut [x, y, ..] pattern shorthand #2401

Closed
wants to merge 8 commits into from

Conversation

Centril
Copy link
Contributor

@Centril Centril commented Apr 15, 2018

๐Ÿ–ผ๏ธ Rendered

๐Ÿ“ Summary

This RFC aims to improve ergonomics by allowing you to write:

// tuples:
let mut (x, y, z) = foo;

// arrays:
let mut [a, b, c] = arr;

๐Ÿ’– Thanks

To @scottmcm, @varkor and others for their review and interesting discussions.

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Apr 15, 2018
@clarfonthey
Copy link
Contributor

clarfonthey commented Apr 15, 2018

While this is (mostly) not a problem any more due to the default match bindings, I think that these rules should apply to ref and ref mut as well. It seems inconsistent otherwise.

@Centril
Copy link
Contributor Author

Centril commented Apr 15, 2018

@clarcharr So the reason why I didn't add that was the default match bindings you mention.
I'm a bit wary of adding syntactic sugar just for the sake of consistency if there won't be anyone who uses it.
Likewise I opted not to allow mut Person { age, name } or mut Foo(x, y) even tho an argument could be made that it would be more consistent to allow that.

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.

@scottmcm
Copy link
Member

scottmcm commented Apr 15, 2018

Hmm, I'm torn about this statement

Likewise I opted not to allow mut Person { age, name } or mut Foo(x, y) even tho an argument could be made that it would be more consistent to allow that.

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 let mut (i, Person { age, name }) = ..., and thus the meaning of this needs to be defined anyway, at which point maybe it'd be better to just do the broader change.

| first binding
```

## Reversed polarity with `immut`
Copy link
Member

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

Copy link
Contributor Author

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
Copy link
Member

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) => {}
    }

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor Author

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?

Copy link
Member

@kennytm kennytm Apr 15, 2018

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 (โ€ฆ).

Copy link
Member

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.

Copy link
Contributor Author

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

@Centril
Copy link
Contributor Author

Centril commented Apr 15, 2018

@scottmcm

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 let mut (i, Person { age, name }) = ..., and thus the meaning of this needs to be defined anyway, at which point maybe it'd be better to just do the broader change.

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

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.
Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Contributor

@durka durka Apr 18, 2018

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.

Copy link
Contributor Author

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.

@petrochenkov
Copy link
Contributor

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.
If a pattern has by-value bindings and all of them are mut then we have a hit. (Combining multiple lets into one tuple let would be kinda cheating, IMO, since it's a stylistic choice.)

@petrochenkov
Copy link
Contributor

By the way, how mut affects by-reference binding, does it make the references mutable?

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.

@Centril
Copy link
Contributor Author

Centril commented Apr 15, 2018

@petrochenkov

But we can actually estimate the effect on the crates.io ecosystem by writing a deny-by-default lint and running it through crater.

Sounds like a nice idea. Would you mind taking a crack at it?

By the way, how mut affects by-reference binding, does it make the references mutable?

I think the interpretation you described is reasonable and is in line with the RFC as currently specified.

Copy link
Contributor

@durka durka left a 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.
Copy link
Contributor

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

Copy link
Contributor Author

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

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.
Copy link
Contributor

@durka durka Apr 18, 2018

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.
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

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.
Copy link
Contributor

@durka durka Apr 18, 2018

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.

Copy link
Contributor Author

@Centril Centril Apr 18, 2018

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.

@blaenk
Copy link
Contributor

blaenk commented Apr 18, 2018

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:

  1. diff_with: https://github.com/bluss/rust-itertools/blob/8be531516fa53551f48747657e0be1c1aeb964bb/src/diff.rs#L46-L48
  2. FormatWith: https://github.com/bluss/rust-itertools/blob/8be531516fa53551f48747657e0be1c1aeb964bb/src/format.rs#L54-L57
  3. minmax_impl: https://github.com/bluss/rust-itertools/blob/8be531516fa53551f48747657e0be1c1aeb964bb/src/minmax.rs#L54-L66
  4. add_scalar, sub_scalar, mul_scalar: https://github.com/bluss/rust-itertools/blob/8be531516fa53551f48747657e0be1c1aeb964bb/src/size_hint.rs#L25

@Centril
Copy link
Contributor Author

Centril commented Apr 18, 2018

@blaenk Fixed; Thanks!

@leoyvens
Copy link

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.

@sergeysova
Copy link

sergeysova commented Apr 27, 2018

What about structure destructuring:

let mut { foo, bar } = value;

@Centril
Copy link
Contributor Author

Centril commented Apr 27, 2018

@sergeysova That pattern { foo, bar } is not legal, so you'd have to do mut MyType { foo, bar }. However, I intend to introduce unnamed structs (with named fields) in another RFC soon.

@SoniEx2
Copy link

SoniEx2 commented May 3, 2018

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.

@Centril
Copy link
Contributor Author

Centril commented May 7, 2018

However, I intend to introduce unnamed structs (with named fields) in another RFC soon.

Update:
I wrote up some plans around this, but they didn't really pan out well;
so it looks like I won't write that RFC after all.

@Centril Centril self-assigned this Aug 9, 2018
@joshtriplett
Copy link
Member

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 mut on the individual elements you want to capture and support subsequent mutation of.

And I don't think we should encourage the use of tuples to cram multiple lets into one, which I think this would encourage.

@SoniEx2
Copy link

SoniEx2 commented Aug 10, 2018

I actually already use if let (Some(x), Some(y)) = ... {} all the time tbh

@bstrie
Copy link
Contributor

bstrie commented Oct 11, 2018

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. let mut (mut x, y) should be an error, as should let mut (ref x, y). Minimizing the potential for subtle and surprising interactions should be the goal. Though truth be told, even though I understand that we can find code that would benefit from this, I'm not sure it really is common enough that it pulls its weight, especially not if it introduces special cases and makes the pattern grammar harder to learn.

@Centril Centril added A-syntax Syntax related proposals & ideas A-patterns Pattern matching related proposals & ideas labels Nov 22, 2018
@graydon
Copy link

graydon commented Jan 12, 2019

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.

@joshtriplett
Copy link
Member

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 mut normally appears. I still stand by my comment at #2401 (comment) . I don't think this is worth expanding the language, just to save a few characters worth of muts.

@rfcbot close

@rfcbot
Copy link
Collaborator

rfcbot commented Jan 18, 2019

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.

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-close This RFC is in PFCP or FCP with a disposition to close it. labels Jan 18, 2019
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 20, 2019

๐Ÿ”” This is now entering its final comment period, as per the review above. ๐Ÿ””

@rfcbot rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Mar 20, 2019
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 30, 2019

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.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this RFC. closed This FCP has been closed (as opposed to postponed) and removed final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. disposition-close This RFC is in PFCP or FCP with a disposition to close it. labels Mar 30, 2019
@rfcbot rfcbot closed this Mar 30, 2019
@Centril
Copy link
Contributor Author

Centril commented Sep 28, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-patterns Pattern matching related proposals & ideas A-syntax Syntax related proposals & ideas closed This FCP has been closed (as opposed to postponed) finished-final-comment-period The final comment period is finished for this RFC. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.