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.