Hmm. I do like that this avoids the need for ‘magic’ scoping rules as would be required for if !let, and it’s even more succinct. On the other hand, it feels a bit weird to be bringing a Perlism to Rust.
The parsing issue is interesting. As has been noted, code of the form:
let Foo(x) = expr || expr2;
parses today as let Foo(x) = (expr || expr2). But I believe this can never type-check, as || always returns a bool, while a pattern of the form Foo(x) can never match a bool. (Handy that we don’t have ‘pattern aliases’, nor do we allow function calls in constant patterns.) So in theory, let could adjust its precedence based on the syntactic form of the pattern.The only patterns a bool can match are variables, constants, and literals. Variables are irrefutable, so not usable with the proposed new feature. Bool literals can be identified at parse time; bool-typed constants can’t, but neither literals nor constants are particularly useful for this feature since they don’t introduce a binding – so it’s ok if, e.g., let true = x || continue continues to parse the old way.
For let Foo(x) = expr || expr2 to parse as (let Foo(x) = expr) || expr, the let Foo(x) = part would have to have stronger precedence than ||. For consistency, you would also expect it to have stronger precedence than anything weaker than ||.
According to the reference, the operators with weaker precedence than || are .. and ..=, assignment operators (=, +=, etc.), return, break, and closures. Assignment operators return () and can’t be matched against Foo(x) either. return, break, and closures are prefix operators, so they can’t conflict with let which is effectively another prefix operator. That leaves .. and ..=, and they actually do pose a problem, as this compiles:
let std::ops::Range { start, end } = 1 .. 2;
And although std::ops::Range { start, end } is irrefutable, it cannot be distinguished at parse time from a refutable enum variant pattern. Thus, adding this feature might require breaking that code (at least, it would require significant additional complexity to not break it). On the other hand, building and then immediately destructuring a range is fairly useless and unlikely to be seen in real code. This could also be postponed until an edition boundary.
In any case, a potential alternative is to just require parentheses:
(let Foo(x) = bar) || continue;
I’d say this is uglier, but it arguably has readability benefits, as it’s more obvious that this is not a regular let, and the || continue sticks out more.