-
Notifications
You must be signed in to change notification settings - Fork 13.4k
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 |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
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.
some initial comments, will do final review once the FCP finishes.
This comment has been minimized.
This comment has been minimized.
79ba69e
to
eb65ac8
Compare
eb65ac8
to
520fe75
Compare
This comment was marked as resolved.
This comment was marked as resolved.
b0364e5
to
520fe75
Compare
35bb20a
to
11320a1
Compare
f70ba19
to
546a82c
Compare
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 have some issues with how the stabilization report and the tests (especially related to this file) are presented.
I think one of the main reasons I was confused about the choice to stabilize let_chains
in the 2024 edition was that it wasn't justified as clear as I wanted to. Having taken another look I figured putting it with my own words might be helpful:
let_chains
was stabilized on the 2024 edition only, partly because #124085 is only on the 2024 edition. That specifically fixes the drop order of the following code: (play)
#![feature(if_let_guard)]
#![feature(let_chains)]
struct LoudDrop(&'static str);
impl LoudDrop {
fn meow(&self) -> bool {
true
}
}
impl Drop for LoudDrop {
fn drop(&mut self) {
print!("{}", self.0);
}
}
fn main() {
if let true = LoudDrop("1").meow()
&& let true = LoudDrop("2").meow()
&& let true = LoudDrop("3").meow()
&& let true = LoudDrop("4").meow()
&& 5 == 2 + 2 {
} else {
let _x = LoudDrop("5");
}
println!();
}
Before edition 2024, this code prints 54321
. On edition 2024, this code prints 43215
. Edition 2024 makes it so that the temporaries created as part of the chain gets dropped before we go to the else block instead of after the else block finishes. #104843 is extremely hard to get fixed before edition 2024, so it was proposed that let chains would only be usable on edition 2024.
Now, coming back to this feature. I was originally confused as to why the "drop order issue" only affected normal if let chains, not while let or if let guards. That's because drop order is only inconsistent when an else
block exists. while let
s do not have else
blocks. if let
guards on matches are clearly scoped to the arms. There is no "drop order MIR ICE issue" on these.
But this stabilization report, and the collection of tests should definitely be improved. Some sections, such as
MIR Generation: The Mid-level Intermediate Representation (MIR) generated for
if let
guards has been carefully reviewed to ensure it aligns with the expected runtime behavior, particularly concerning variable lifetimes and drop order.
and
Unlike
let chains
inwhile
and regularif
the drop order inif let
guard is predictable and stable across all editions and even if we writes let chains inif let
guard it also works stable across all editions, this was tested by #140981 and compare-drop-order.rs
read like fluff to me. The relationship between drop-order.rs
and compare-drop-order.rs
was also confusing to me, before taking a closer look to realize that drop-order.rs
compares the drop order temporaries as part of a normal if
boolean versus an if let
in a guard, whereas compare-drop-order.rs
compares drop orders in an if let
guard over an if let
nested in the match.
Except it actually doesn't test the real issue. Here's a different test I wrote (play):
#![feature(if_let_guard)]
#![feature(let_chains)]
struct LoudDrop(&'static str);
impl LoudDrop {
fn louder(&self) -> LoudDrop {
Self(Box::leak(format!("({})", self.0).into_boxed_str()))
}
}
impl Drop for LoudDrop {
fn drop(&mut self) {
print!("{}", self.0);
}
}
fn main() {
match () {
() if let Some(_a) = Some(LoudDrop("1").louder()) &&
let Some(_b) = Some(LoudDrop("2").louder()) &&
let 5 = 2 + 2 => (),
() if let Some(_a) = Some(LoudDrop("3").louder()) &&
let Some(_b) = Some(LoudDrop("4").louder()) => (),
() => ()
}
println!();
if let Some(_a) = Some(LoudDrop("1").louder()) &&
let Some(_b) = Some(LoudDrop("2").louder()) && let 5 = 2+2 {
}
if let Some(_a) = Some(LoudDrop("3").louder()) &&
let Some(_b) = Some(LoudDrop("4").louder()) {
}
println!();
}
On edition 2024 it prints
(2)2(1)1(4)4(3)3
(2)2(1)1(4)4(3)3
But before edition 2024 it prints
(2)2(1)1(4)4(3)3
(2)(1)21(4)(3)43
This is part of the issue with the drop order changes. These tests are not targeted enough for the subtlety at play with how consistent if let
guard appears with let_chains
. In fact, these tests do not test chains at all, and I was primarily concerned with how these components interact.
There are also several issues with the way some tests are described:
exhaustive.rs - validates that if let guards do not affect exhaustiveness checking in match expressions, test fails due missing match arm
This is weird. I would normally write, "validates that if let guards are correctly treated in exhaustiveness checking"
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 behavior
The test is saying that a conditional move that happens in an if let
guard does not affect other arms that try to use the same variable being moved. I wouldn't describe it as "false move errors or unsound behavior".
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 the let 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 clarity
reads like fluff. warns.rs
does not "ensure soundness". It checks that irrefutable guards and unreachable patterns in guards are correctly warned. (was there rationale and justification for not warning let guards that are irrefutable but bind to some variable? maybe w.r.t moves and stuff, unexplored) guard-mutability-2.rs
is not about syntax rules it is about ownership/mutability.
I'm not sure how to proceed and perhaps this is a reason why writing stabilization reports are discouraged for people who are less familiar with the feature. Perhaps it would read better to me if it was rewritten or have the descriptions reworked, but I'm not sure who should be responsible for doing that. And I'm not actually sure if that is necessary given that people on lang already ticked their checkboxes.
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've asked here in this thread about mir tests, i got clear answer that current tests are fine and we dont need more, i did compare mir during pre stabilization to remove some concerns about this, and again, ive asked about adding mir tests here, got answer that we dont need this
compare-drop-order.rs was originally created to show that there is no difference between if let inside match arm and if let guard, to show that they are the same in terms of drop order, this concerns was from @Nadrieril. this test wasnt mean to test let chains inside if let guard, because there is already a test for it #140981
i agree about not best description
reads like fluff. warns.rs does not "ensure soundness". It checks that irrefutable guards and unreachable patterns in guards are correctly warned. (was there rationale and justification for not warning let guards that are irrefutable but bind to some variable? maybe w.r.t moves and stuff, unexplored) guard-mutability-2.rs is not about syntax rules it is about ownership/mutability.
except this part, this is overview of all this tests, it's not a describe of each test, it doesnt mean that warns.rs shows exatcly this and only need for "ensure soundness", i said about this word for word about this test little bit before this part
The compiler correctly diagnoses irrefutable patterns, unreachable patterns
also, i want to acknowledge that a big part of the problem here is simply my inexperience, this is my first time writing a stabilization report, i’ve tried to do my best here, i read many past stabilization PRs before drafting this one, but since there’s no standardized template for stabilization PRs, some parts (especially the descriptions) were challenging
id love to improve this and learn the best practices from those with more experience, im definitely open to questions or suggestions for clarifying parts of the report beyond the points you already mentioned, which i really appreciate
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.
reads like fluff
Yeah @Kivooeo 's writings read a bit as if they were LLM enhanced, which sometimes make them less pleasant to read. However that shouldn't itself be a reason to reject the stabilization proposal itself. the intent, covered areas, content, etc of course can always see improvements, but I'd say it's enough for a stabilization report. One needs to give people room to grow and improve, and @Kivooeo has demonstrated willingness of that.
Except it actually doesn't test the real issue. Here's a different test I wrote [...]
Such a comparison is included via #140981 / dcd2736, namely in drop-order-comparisons.rs
where:
t_guard_if_let_chains_then_else
is a clone of the edition 2024 variant oft_if_let_chains_then_else
, but it's edition independent so it demonstrates that the drop order is the same across all editions.- same for
t_guard_if_let_chains_then
andt_if_let_chains_then
: we see that edition 2024 drop order prevails.
Note that @Kivooeo linked #140981 in the snippet you were quoting, so the link to a "complicated" drop order test was there.
I was originally confused as to why the "drop order issue" only affected normal if let chains, not while let or if let guards. That's because drop order is only inconsistent when an else block exists.
while lets
do not haveelse
blocks.if let
guards on matches are clearly scoped to the arms. There is no "drop order MIR ICE issue" on these.
As you can see with t_if_let_chains_then
, and the example you gave, if let
chains do have differing drop order on different editions, even if there is no else
block. I have made statements in the past, esp in #140204, that indicate that the absence of an else
is enough to get 2024 drop order also on edition 2021, but my understanding has evolved since then. I was wrong, I'm sorry. Please don't get confused by them.
What stopped #140204 from progressing is that the ICE and compilation error inconsistency persists with while let chains, which are the reasons for why we can't stabilize if let
chains on 2021. If you look at #140981, it adds tests that show that the match guard variants of those specific examples work both on edition 2021 and edition 2024. I can't find a single test that shows any if let guards edition inconsistency wrt drop order or scoping. So that's why I said it's fine to stabilize them, esp with the experience behind me of all the weird drop order edge cases encountered with if let
.
Also, if you take a look, while let is affected by the drop order rescoping. that example has while let
give the same output as if let
, i.e. edition inconsistent, while if let guard chains are edition consistent and match edition 2024 behaviour of if let
and while let
. Maybe a possible resolution of #140204 is to apply edition independent drop order rescoping for while
. If that doesn't break anyone (as while
doesn't have an else
), we can roll it out to get while let
chains stable across editions. IDK, it needs further experiments.
I hope I could make it a little bit more clear why I think that it's fine to stabilize if let guards on 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.
i updated description, please take a look, it should be a way better now, more straightforward and clear to readers
i also wondering, if i should mention such case, because i said earlier that regular if let
and if let
guard are the same in terms of drop order, which is true when we are not using let chains
inside, for example it wont be true that drop order is the same here for all editions if i understand that correctly, but not sure if i should mention this in description or write a test for it?
match Some(2) {
Some(x)
if let Some(y) = Some(3)
&& y > 5
&& x > 5 => {}
_ => (),
}
match Some(2) {
Some(x) => {
if let Some(y) = Some(3)
&& y > 5
&& x > 5
{}
}
_ => (),
}
Summary
This proposes the stabilization of
if let
guards (tracking issue: #51114, RFC: rust-lang/rfcs#2294). This feature allowsif let
expressions to be used directly within match arm guards, enabling conditional pattern matching within guard clauses.What is being stabilized
The ability to use
if let
expressions within match arm guards.Example:
Motivation
The primary motivation for
if let
guards is to reduce nesting and improve readability when conditional logic depends on pattern matching. Without this feature, such logic requires nestedif let
statements within match arms:Implementation and Testing
The feature has been implemented and tested comprehensively across different scenarios:
Core Functionality Tests
Scoping and variable binding:
scope.rs
- Verifies that bindings created inif let
guards are properly scoped and available in match armsshadowing.rs
- Tests that variable shadowing works correctly within guardsscoping-consistency.rs
- Ensures temporaries in guards remain valid for the duration of their match armsType system integration:
type-inference.rs
- Confirms type inference works correctly inif let
guardstypeck.rs
- Verifies type mismatches are caught appropriatelyPattern matching semantics:
exhaustive.rs
- Validates thatif let
guards are correctly handled in exhaustiveness analysismove-guard-if-let.rs
andmove-guard-if-let-chain.rs
- Test that conditional moves in guards are tracked correctly by the borrow checkerError Handling and Diagnostics
warns.rs
- Tests warnings for irrefutable patterns and unreachable code in guardsparens.rs
- Ensures parentheses aroundlet
expressions are properly rejectedmacro-expanded.rs
- Verifies macro expansions that produce invalid constructs are caughtguard-mutability-2.rs
- Tests mutability and ownership violations in guardsast-validate-guards.rs
- Validates AST-level syntax restrictionsDrop Order and Temporaries
Key insight: Unlike
let_chains
in regularif
expressions,if let
guards do not have drop order inconsistencies because:drop-order.rs
- Tests that temporaries in guards are dropped at the correct timecompare-drop-order.rs
- Compares drop order betweenif let
guards and nestedif let
in match arms, confirming they behave identically across all editionslet chain
was made by @est31Edition Compatibility
This feature stabilizes on all editions, unlike
let_chains
which was limited to edition 2024. This is safe because:if let
guards don't suffer from the drop order issues that affectedlet_chains
in regularif
expressionsInteractions with Future Features
The lang team has reviewed potential interactions with planned "guard patterns" and determined that stabilizing
if let
guards now does not create obstacles for future work. The scoping and evaluation semantics established here align with what guard patterns will need.Unresolved Issues
All blocking issues have been resolved:
let chains
insideif let
guard is the sameRelated:
if let
guards documentation reference#1823