-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Stabilize if let
guards (feature(if_let_guard)
)
#141295
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
base: master
Are you sure you want to change the base?
Conversation
r? @SparrowLii rustbot has assigned @SparrowLii. Use |
Some changes occurred to the CTFE machinery Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt Some changes occurred in compiler/rustc_codegen_ssa |
6fe74d9
to
5ee8970
Compare
rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead. cc @rust-lang/rust-analyzer Some changes occurred in src/tools/clippy cc @rust-lang/clippy |
eb0e4b4
to
0358002
Compare
This comment has been minimized.
This comment has been minimized.
92a5204
to
ab138ce
Compare
This comment has been minimized.
This comment has been minimized.
5ceca48
to
a20c4f6
Compare
This comment has been minimized.
This comment has been minimized.
1dd9974
to
5796073
Compare
cc @Nadrieril |
This needs a fcp so I'd like to roll this to someone more familiar with this feature |
r? @est31 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(similarly using a random file to get an inline comment)
Could you distill the contents of the "Edition Considerations" section? This section needs to say which editions this will be stable in and why, which should be concise. I'm having a very tough time following wording like "...designed to integrate seamlessly across all Rust editions" when there isn't a simple statement like "x will be stable in [all editions][edition xxxx and later]".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, i just later realise that we didnt tested this in 2015 and 2018, i will update this block sure
if let
guard if let
guards (feature(if_let_guard)
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(from the top post)
The primary blocker identified during pre-stabilization testing was a set of subtle interactions related to drop order and variable scoping within match guards with let chains, comprehensively addressed and validated by the tests introduced in #140981. With this crucial aspect resolved, no other significant issues remain, and the feature is considered ready for stabilization.
Testing the current design is necessary, but it's much more important that you convince us why the current design is correct and why we don't need to iterate further - which no test in the world can do :).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I’m still trying to get a better grasp of what exactly counts as the “current design” in this context — whether it refers mostly to syntax, semantics, implementation details (like MIR/lowering), or a combination of all of these. I’ve mainly been focusing on making sure things work and are well-tested, but I now realize that’s not the same as being able to clearly explain why the design itself is correct and stable
I’d really appreciate any pointers or examples of what kind of justification would be most helpful for the team here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The things that matter here are mostly syntax and semantics, since that is the part we won't be able to change in the future. Lowering can come a bit into play here since drop order interacts with MIR.
As some twocents, the things you may want to consider mentioning are things like consistency with existing language behavior and anything that might be a "gotcha" for users. For example, can if_let_guards
(if let
on LHS of the =>
) always be thought of as if let
within a match
arm's expression (if let
on the RHS of the =>
)? What order do things get dropped in? Are there any behavior differences across editions that will be user-visible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the clarification — that helps a lot
Regarding the syntax and semantics: the final form of the syntax was actually proposed back in the original RFC from 2018, so in terms of syntax, things have been stable and intentional from the start
As for semantics as far as I concerned, the semantics between using if let
guards on the LHS and an if let
inside the match arm body (RHS) are intended to be equivalent in terms of behavior visible to the user. Both forms bind variables and control flow similarly, and they should produce the same observable effects
I've tested drop order for LHS and RHS cases and they are identical
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I don't expect syntax to change, it's fine to say that it is a logical extension to if
guards that hasn't had any bikeshed. The other two are a bit trickier since it's about identifying possible edge cases. E.g. if this is to be stable in all editions, it would be useful to see examples showing how the changes to if let
temporary scope come into play.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, yes they are identical, if we change
match ... {
<pat> if <guard> => { <arm> }
_ => {}
}
in tests to
match ... {
<pat> => if <guard> {
<arm>
},
_ => {}
}
nothing will change in drop order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Nandi may be asking for a more first principals approach, e.g. does the compiler source use the same paths for both so we have a better idea that the results will always be identical? Or at least pointing to an existing test (or a new one) that shows the two patterns generate the same MIR in all editions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I will add test that tests drop order between if let
guard and if let
in match arm across all editions, that shows that they are the same, I'm not sure how can I make test for MIR but make a test that just shows that they have the same drop order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please take a look at this test, if this is ok
compare-drop-order.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, yes they are identical
Nice! That's all I wanted to make sure, this test looks ok to me.
Actually that can't be fully true because 1 | 1 if guard()
can run the guard twice whereas 1 | 1 => if guard() { ... }
will only run the guard once, but the basic idea is there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for letting us know that this is your first stabilization PR. I'll quickly point out two things:
(1) You claim in your PR description that
Multiple let bindings can be chained within a single guard using the && operator (only in 2024 edition, since let chains were stabilized only in 2024 edition).
.. This is not the case as shown in this file. This file is being run on 2015 edition and if merged now, we would accidentally stabilize let chains with the wrong drop order (and potentially broken MIR in some cases)
This was a subtle thing in the previous PR that stabilized let_chains in 2024 that made this permissible on all editions. See my comment at that time. If stabilizing this, that CondChecker
should have LetChainsPolicy::EditionDependent
instead.
(2) When stabilizing you should remove the code that gates the feature. As far as I can see, that code lives under https://github.com/rust-lang/rust/blob/master/compiler/rustc_parse/src/parser/expr.rs#L3407-L3436. Remove the code that checks for if let guards and adds it to gated spans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your reply, let me check if I got you correctly
I should change this
LetChainsPolicy::AlwaysAllowed
to
LetChainsPolicy::EditionDependent
And that's it, right? So it will fix ability of if-let
guards chaining on all editions which was added by accident
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's one part of it to fix (1). You must also make tests over revisions so that they can be tested on multiple editions to properly make sure that the gating does happen <2024 but is not gated on 2024.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So after removing gate and AlwaysAllowed
method now looks like this
fn parse_match_arm_guard(&mut self) -> PResult<'a, Option<P<Expr>>> {
// Used to check the `if_let_guard` feature mostly by scanning
// `&&` tokens.
if !self.eat_keyword(exp!(If)) {
// No match arm guard present.
return Ok(None);
}
let mut cond = self.parse_match_guard_condition()?;
CondChecker::new(
self,
LetChainsPolicy::EditionDependent { current_edition: Edition::Edition2024 },
)
.visit_expr(&mut cond);
Ok(Some(cond))
}
not sure about comment here and should I use Edition2024
or EditionFuture
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.. This is not the case as shown in this file. This file is being run on 2015 edition and if merged now, we would accidentally stabilize let chains with the wrong drop order (and potentially broken MIR in some cases)
This was a subtle thing in the previous PR that stabilized let_chains in 2024 that made this permissible on all editions. See my comment at that time. If stabilizing this, that CondChecker should have LetChainsPolicy::EditionDependent instead.
@fee1-dead note that we actually do have post-2024 drop order on all editions with if let guard chains. All the examples where while let
and/or if let
have weird behaviour or even broken mir work just fine with if let guards. See the tests added by #140981 .
So it's absolutely fine to stabilize if let guard chains on all editions. The PR description needs to be amended, of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, actually, this is what i was thinking when was removing this gate, like "why if drop order is working stable across all editions in if let
guard already, wouldnt it be also stable for all editions for let chains
". seems like we can also stable let chains
across all editions as well, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can't stabilize let chains
on all editions because the drop order differs between editions, while it doesn't differ for if let guard chains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so two questions then
- can we make it work the same in all editions, or it's too complex
- should i return
LetChainsPolicy::AlwaysAllowed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and yes i will add information about this to pr description later today
ec70e11
to
aef3f5f
Compare
This comment has been minimized.
This comment has been minimized.
bb5916a
to
49046a0
Compare
Musing: it's not obvious to me that stabilizing this in all editions is the best answer, even if I agree it's technically legal. How should I weigh the potential confusion of "wait, I moved it from a normal |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
831fca2
to
6270aba
Compare
266e9d9
to
58b02c9
Compare
Thanks for your comment! Just to clarify, the drop order appears to be consistent across all editions. Could you please clarify what you mean by the potential confusion or breaking change for users? Are you referring to specific behavioral differences or edge cases that might cause code to behave differently when moving from a normal It would be helpful to understand the exact scenarios you’re concerned about |
This comment has been minimized.
This comment has been minimized.
fdec32f
to
ba28c26
Compare
This comment has been minimized.
This comment has been minimized.
0699298
to
d6688ef
Compare
We reviewed this briefly in the lang triage call today. We'll all probably need to have a closer look at this, and we'll of course be particularly interested in confirming that the drop order here is what we expect. |
I'm missing context, what breakage are you talking about? @Kivooeo has confirmed that moving from a |
@traviscross I need your advice on this, would writing MIR drop order test for if-let guard will help team review it better and more precisely along side two existed (non MIR tests) that created for drop order: compare-drop-order, drop-order The same way it was made for let chains back in the day here mir-match-guard-let-chains-drop-order |
@Kivooeo the test you link is not a mir test, it's a normal (ui) test in the "mir" directory. A real MIR test is not necessary for this feature, since drop order can be observed with normal tests such as the ones you point to. It also seems to me that the existing tests are sufficient to demonstrate the drop order, unless there's a corner case I'm not thinking of. |
@Nadrieril,
Well, I thought that this is a MIR test because it was in mir folder, hm, interesting, is there a real MIR tests? I guess it should do something like comparing right MIR output to that one that created by programm or something
|
@traviscross as a good starting point, you can look at the tests added by #140981. The PR description gives an overview. |
Summary
This proposes the stabilization of
if let
guards (tracking issue: #51114, RFC: rust-lang/rfcs#2294). This feature enhances Rust's pattern matching by allowingif let
expressions to be used directly within match arm guards, enabling more expressive, concise, and readable conditional logic in match statements.What is being stabilized
The ability to use
if let
expressions within match arm guards is being stabilized. This allows for conditional pattern matching directly within the guard clause of a match arm, combining pattern destructuring with boolean conditions.Example:
Motivation
The primary motivation for if let guards is to reduce boilerplate and improve the clarity of conditional logic within match statements. Prior to this feature, complex conditional checks often required nested if let statements within the match arm's body, leading to increased indentation and reduced readability.
Consider the following scenario without if let guards:
With if let guards, this becomes significantly more streamlined:
Also I should make a remark here and say that drop order or other things are identical in this both contructions, so think about
if let
guards as just like about easirer way to express thisImplementation and Testing
The if let guard feature has undergone extensive implementation and testing to ensure its stability, correctness, and consistent behavior across all Rust editions. This process has involved collaborative efforts, notably with @est31, who has provided invaluable insights and thorough testing, confirming identical functionality across editions.
Tests
Error messages and diagnostics
warns.rs, parens.rs, macro-expanded.rs, guard-mutability-2.rs, ast-validate-guards.rs - shows that
if let
guards are parsed with strict syntax rules, disallowing parentheses around thelet
expression and rejecting macro expansions that produce statements instead of expressions. The compiler correctly diagnoses irrefutable patterns, unreachable patterns, mutability violations, and unsupported constructs inside guards, ensuring soundness and clear error messages. The parser fails on invalid syntax without complex recovery, preventing cascading errors and maintaining clarityScoping and shadowing
scope.rs - verifies that bindings created inside if let match guards are properly scoped and usable in the corresponding match arm. Covers both let on the left-hand side and right-hand side of &&
shadowing.rs - validates that name shadowing works correctly within if let guards and does not lead to resolution issues. Demonstrates deep shadowing via multiple lets and ensures type resolution matches expected shadowed bindings
scoping-consistency.rs - ensures that temporaries created within
if let
guards are correctly scoped to the guard expression and remain valid for the duration of the match arm they’re used inExhaustiveness
exhaustive.rs - validates that
if let
guards do not affect exhaustiveness checking in match expressions, test fails due missing match armType System
type-inference.rs - confirms that type inference works correctly in match guards using
if let
typeck.rs - verifies that type mismatches in
if let
guards are caught as expected — the same way they are in other contextsDrop
drop-order.rs - ensures that temporaries created in match guards are dropped in the correct order
compare-drop-order.rs - comparing drop order between regular
if let
in match arm andif let
guard between all editions to show that drop order across all editions the samedrop-score.rs - ensures that temporaries introduced in
if let
guards (includinglet chains
) live for the duration of the armMove
move-guard-if-let.rs, move-guard-if-let-chain.rs - tests verify that the borrow checker correctly understands how moves happen inside
if let
guards, especially withlet
chains. Specifically, a move of a variable inside a guard pattern only actually occurs if that guard pattern matches. This means the move is conditional, and the borrow checker must track this precisely to avoid false move errors or unsound behaviorKey aspects verified during testing include:
&&
: Multiplelet
bindings can be chained within a single guard using the&&
operator (only in 2024 edition, sincelet chains
were stabilized only in 2024 edition).if let
guard can be refutable, allowing for concise conditional logic based on the success or failure of the pattern match.if let
guards has been carefully reviewed to ensure it aligns with the expected runtime behavior, particularly concerning variable lifetimes and drop order.Edition Considerations
This feature is stable in all Rust editions. Any initial concerns regarding temporary drop order or variable scoping in older editions were resolved by backported compiler improvements, ensuring consistent and correct behavior across all editions. Users can adopt this feature immediately without needing to migrate their projects to a newer edition
Unresolved Issues
Note
This is my first stabilization PR, so if I missed any steps or there’s something I should adjust, please feel free to point it out — I’d be happy to improve it. I’ve followed the standard process as closely as I could, but I’m still learning the full stabilization workflow
Related
if let
guards documentation reference#1823 reviewed by @WaffleLapkin (we put it off untill this feature get stabilized)if let
overall, that was a good discussion where we tested things