Idea: PlacePattern

Then we would have two syntaxes for the same purpose: let (foo, bar) and (let foo, let bar). Some people might find this confusing (especially people who are just learning the language). We could deprecate let (foo, bar) in a new edition, but I believe many Rustaceans wouldn't like that.

1 Like

So, it sounds like there are two separate ideas here, both of which seem worth considering and potentially RFCing.

  1. The in pattern: anywhere you could put an identifier for a fresh variable name, you can instead put in followed by an existing path to store the matched value into that existing path as though via an assignment. You can put multiple in matches and non-in matches in the same expression, and they'll get assigned or created (respectively) left-to-right. You can use in with | patterns, and only the in matches in the matching branch of the | will take effect. This works in any pattern-matching context (though it requires parentheses for disambiguation in a for loop, for (in x) in ...).

  2. @comex's suggestion to allow simple aggregate assignment of tuples, or potentially other irrefutable patterns. Note that we need to carefully define this if we want (x, y) = (y, x) to do the right thing. What about (x, x.a) = (m, n); what behavior should that have? Can we define semantics that do the right thing for both of those? I think the rule we want is that the entire right-hand side is evaluated first, then all the assignments take place. (I think we want the same rule for let and in patterns in (1) above.)

With my language team hat on (but not speaking for the rest of the language team): I'd love to see RFCs for both of these. I can imagine accepting one or accepting both, as they have somewhat different use cases. (1) works well in general pattern matching; (2) works well for simple assignment of multiple values.

1 Like

It doesn't require parenthesis. I tried this out in libsyntax (parse_pat_with_range_pat)

             _ => if self.eat_keyword(kw::Underscore) {
                 // Parse _
                 PatKind::Wild
+            } else if self.eat_keyword(kw::In) {
+                let _ = self.parse_ident()?; // TODO; allow `p.f` as well
+                PatKind::Wild
             } else if self.eat_keyword(kw::Mut) {

and both let (in x, in y) = (0, 1) and for in x in 0..1 {} parse completely fine. I also don't think such a special case should be added to the for loop grammar because it would open up another entry point into the pattern grammar and further unnecessarily complicate it.

That brings up the case if let (A(in x), B(_)) = expr { ... }. It seems to me that no mutation should happen if the whole pattern doesn't match so that there's some atomicity about assignments in pattern matching. Otherwise, patterns themselves become rather side-effectful (tho this might be a good reason to go with @comex's idea instead and it would allow more general place expressions). I think @comex's idea should be grammatically just $expr = $expr; -- that is, we don't change the syntax of assignment at all, but rather just change the lowering to allow ($e_0, ..., $e_n) to desugar into more assignments (although now one needs to take into account [$e_0, ..., $e_n] and friends as well as _).

But please not now. :slight_smile: we have enough ongoing implementation work on control flow and pattern matching as-is.

1 Like

If destructuring assignment didn't require let, would this also apply to if let and while let? E.g.

if Ok(let foo) = bar {}
if "foo" = bar {}
if foo = bar {}  // assigns bar to foo; likely a bug!

The compiler can parse it unambiguously, but allowing it without parentheses would make parse errors more confusing, in my opinion. I think it'd be worth requiring the parenthesis.

Agreed.

No, destructuring assignment would still evaluate to () so it wouldn't work in an if or other expression.

1 Like

Do you mean compiler errors (which ones, specifically?) or difficulties for humans reading it?

The parser recovery e.g. for for ( x in y ) { ... } introduced in Syntax: Recover on `for ( $pat in $expr ) $block` by Centril · Pull Request #62928 · rust-lang/rust · GitHub took a minute to fix, and I don't imagine it would be substantially more difficult to fix other ones and provide targeted diagnostics where necessary.

As for humans reading it, I think if people think parsing for in x in iter { ... } is difficult then most people will not write it that way and include parenthesis (we can tweak the lint to not trigger in this case). Also, for in x in iter { ... } seems like an uncommon thing to do (just as let in x = 0; would be uncommon, because you can just use normal assignment instead...) so I think the requirement wouldn't protect users from a common pitfall (and then there's the type system protection as well). It does however substantially complicate the code in parser/pat.rs (and the grammar it is based on) because it is inconsistently requiring parenthesis just for for-loops but not in other places. All in all, I disagree that the requirement here is worth it.

1 Like

TBH, I find the overloading of in confusing here, esp. within for loops.

@comex's suggestions are IMHO more elegant, but if patterns in pure assignments are not feasible, how about a hybrid approach where we use mut as introductory keyword for such pattern assigments (as suggested elsewhere), and optionally let within such mut assignments to introduce new variables:

mut (a, let b) = foo()
if mut x = foo() { ... }
if mut (A(x), B(let y)) = foo() { ... }
for mut x in iter { ... }

That doesn't work because mut already has meaning in patterns.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3f3dad4ed64f743252333cb1fc394e55

Both. The compiler can't as easily know what you meant to give a clear compiler error, and the human can't parse it as easily.

Maybe -- Let's be specific tho. Which compiler error (e.g. in the parser) or one that you would like to see would become more difficult to encode? As I noted above, I already fixed one diagnostic in the libsyntax code (was trivial to do so) which related to for-loops.

With any grammar there will always be corner cases more difficult to parse but they are allowed nonetheless because it would be hacky not to allow them and because these corner cases are uncommon (see aforementioned rationale, if you disagree with it I'd like to understand why).

error: expected pattern, found keyword `in`
 --> src/main.rs:2:9
  |
2 |     for in vec![1, 2, 3] {
  |         ^^ expected pattern

Assuming that we don't make error messages like the above any worse or more ambiguous, I don't have a problem with allowing for in x in y in the grammar and then steering people away from it and towards for (in x) in y with lints.

That's a pretty bad diagnostic actually, as it is fatal and not performing any recovery. We can do better by parsing for $pat and then seeing that there is a missing in after the vec![...] (in fact, parse_for_expr already has such logic). We can make the diagnostic even better by looking at the pat: P<Pat> behind us and see if is in $place and then suggesting that the in be moved to after the $place together with a suggested pattern to match on. (Of course, we have to see whether this is a common pitfall that deserves a special cased diagnostic but I'm open to implementing it myself if users need it.)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.