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

Open
wants to merge 8 commits into
base: master
from

Conversation

Projects
None yet
@Centril
Copy link
Contributor

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 label Apr 15, 2018

@clarfon

This comment has been minimized.

Copy link
Contributor

clarfon 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

This comment has been minimized.

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

This comment has been minimized.

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`

This comment has been minimized.

@kennytm

kennytm Apr 15, 2018

Member

We could use const (as in *mut T vs *const T) instead of introducing yet another keyword. (I'm πŸ‘Ž to allowing reversed polarity anyway).

This comment has been minimized.

@Centril

Centril Apr 15, 2018

Author Contributor

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

This comment has been minimized.

@kennytm

kennytm Apr 15, 2018

Member

We have recently implemented #554 (rust-lang/rust#48500). Would mut next to a parenthesis pattern be allowed?

    match 5 {
        mut (x) => {}
    }

This comment has been minimized.

@Centril

Centril Apr 15, 2018

Author Contributor

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.

This comment has been minimized.

@kennytm

kennytm Apr 15, 2018

Member

@Centril the *.y file is not up-to-date though, and (x) isn't a tuple (the 1-tuple syntax is ($pat,)).

This comment has been minimized.

@Centril

Centril Apr 15, 2018

Author Contributor

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?

This comment has been minimized.

@kennytm

kennytm Apr 15, 2018

Member

@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 (…).

This comment has been minimized.

@scottmcm

scottmcm Apr 16, 2018

Member

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.

This comment has been minimized.

@Centril

Centril Apr 16, 2018

Author Contributor

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

This comment has been minimized.

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.

This comment has been minimized.

@scottmcm

scottmcm Apr 15, 2018

Member

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

This comment has been minimized.

@Centril

Centril Apr 15, 2018

Author Contributor

Yeah; I think your comment reflects what I actually meant ;)

This comment has been minimized.

@durka

durka Apr 18, 2018

Contributor

This further reduces the usefulness to macros, though I guess the double-mut warnings could be turned off inside macros.

This comment has been minimized.

@Centril

Centril Apr 18, 2018

Author Contributor

@durka The standard approach is that you can disable lints with #[allow(..)], and that should be doable inside macros as usual.

@petrochenkov

This comment has been minimized.

Copy link
Contributor

petrochenkov commented Apr 15, 2018

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

This comment has been minimized.

Copy link
Contributor

petrochenkov commented Apr 15, 2018

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

This comment has been minimized.

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.

@durka
Copy link
Contributor

durka left a comment

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.

This comment has been minimized.

@durka

durka Apr 18, 2018

Contributor

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

This comment has been minimized.

@Centril

Centril Apr 18, 2018

Author Contributor

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.

This comment has been minimized.

@durka

durka Apr 18, 2018

Contributor

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.

This comment has been minimized.

@durka

durka Apr 18, 2018

Contributor

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

This comment has been minimized.

@Centril

Centril Apr 18, 2018

Author Contributor

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

This comment has been minimized.

@durka

durka Apr 18, 2018

Contributor

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.

This comment has been minimized.

@Centril

Centril Apr 18, 2018

Author Contributor

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.

This comment has been minimized.

@durka

durka Apr 18, 2018

Contributor

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.

This comment has been minimized.

@Centril

Centril Apr 18, 2018

Author Contributor

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Contributor Author

Centril commented Apr 18, 2018

@blaenk Fixed; Thanks!

@leodasvacas

This comment has been minimized.

Copy link

leodasvacas commented Apr 19, 2018

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

This comment has been minimized.

Copy link

sergeysova commented Apr 27, 2018

What about structure destructuring:

let mut { foo, bar } = value;
@Centril

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Member

joshtriplett commented Aug 10, 2018

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

This comment has been minimized.

Copy link

SoniEx2 commented Aug 10, 2018

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

@bstrie

This comment has been minimized.

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.

@graydon

This comment has been minimized.

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

This comment has been minimized.

Copy link
Member

joshtriplett commented Jan 18, 2019

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

This comment has been minimized.

Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment