-
Notifications
You must be signed in to change notification settings - Fork 13.6k
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 |
(little off-topic but i have to say it) Thank you for the kind words! I also hope I’ll be able to return to working on Rust project. I still have some ambitious plans for reorganizing the tests that I wanted to finish this summer, but unfortunately I’ll have to postpone them until next year. Regarding my previous message: I think it can serve as a good new starting point and help anyone interested in stabilizing this feature understand where things stand right now. I tried to summarize everything that’s currently relevant and that might help going forward. Thanks again to everyone — I’ll definitely be back to work on Rust! |
add a scope for `if let` guard temporaries and bindings This fixes my concern with `if let` guard drop order, namely that the guard's bindings and temporaries were being dropped after their arm's pattern's bindings, instead of before (#141295 (comment)). The guard's bindings and temporaries now live in a new scope, which extends until (but not past) the end of the arm, guaranteeing they're dropped before the arm's pattern's bindings. So far, this is the only way I've thought of to achieve this without explicitly rescheduling guards' drops to move them after the arm's pattern's. I'm not sure this should be merged as-is. It's a little hacky and it introduces a new scope for *all* match arms rather than just those with `if let` guards. However, since I'm looking for feedback on the approach, I figured this is a relatively simple way to present it. As I mention in a FIXME comment, something like this will be needed for guard patterns (#129967) too[^1], so I think the final version should maybe only add these scopes as needed. That'll be better for perf too. Tracking issue for `if_let_guard`: #51114 Tests are adapted from examples by `@traviscross,` `@est31,` and myself on #141295. cc, as I'd like your input on this. I'm not entirely sure who to request for scoping changes, but let's start with r? `@Nadrieril` since this relates to guard patterns, we talked about it recently, and rustbot's going to ping you anyway. Feel free to reassign! [^1]: e.g., new scopes are needed to keep failed guards inside `let` chain patterns from dropping existing bindings/temporaries; something like this could give a way of doing that without needing to reschedule drops. Unfortunately it may not help keep failed guards in `let` statement patterns from dropping the `let` statement's initializer, so it isn't a complete solution. I'm still not sure how to do that without rescheduling drops, changing how `let` statements' scopes work, or restricting the functionality of guard patterns in `let` statements (including `let`-`else`).
87bedfc
to
59125c0
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
59125c0
to
e7dd467
Compare
This comment has been minimized.
This comment has been minimized.
e7dd467
to
48dbd58
Compare
This comment has been minimized.
This comment has been minimized.
48dbd58
to
a144613
Compare
This comment has been minimized.
This comment has been minimized.
a144613
to
63476e5
Compare
This comment has been minimized.
This comment has been minimized.
63476e5
to
e34fd88
Compare
☔ The latest upstream changes (presumably #143766) made this pull request unmergeable. Please resolve the merge conflicts. |
e34fd88
to
bde3195
Compare
☔ The latest upstream changes (presumably #144526) made this pull request unmergeable. Please resolve the merge conflicts. |
add a scope for `if let` guard temporaries and bindings This fixes my concern with `if let` guard drop order, namely that the guard's bindings and temporaries were being dropped after their arm's pattern's bindings, instead of before (#141295 (comment)). The guard's bindings and temporaries now live in a new scope, which extends until (but not past) the end of the arm, guaranteeing they're dropped before the arm's pattern's bindings. This only introduces a new scope for match arms with guards. Perf results (#143376 (comment)) seemed to indicate there wasn't a significant hit to introduce a new scope on all match arms, but guard patterns (#129967) will likely benefit from only adding new scopes when necessary (with some patterns requiring multiple nested scopes). Tracking issue for `if_let_guard`: #51114 Tests are adapted from examples by `@traviscross,` `@est31,` and myself on #141295.
add a scope for `if let` guard temporaries and bindings This fixes my concern with `if let` guard drop order, namely that the guard's bindings and temporaries were being dropped after their arm's pattern's bindings, instead of before (#141295 (comment)). The guard's bindings and temporaries now live in a new scope, which extends until (but not past) the end of the arm, guaranteeing they're dropped before the arm's pattern's bindings. This only introduces a new scope for match arms with guards. Perf results (#143376 (comment)) seemed to indicate there wasn't a significant hit to introduce a new scope on all match arms, but guard patterns (#129967) will likely benefit from only adding new scopes when necessary (with some patterns requiring multiple nested scopes). Tracking issue for `if_let_guard`: #51114 Tests are adapted from examples by `@traviscross,` `@est31,` and myself on #141295.
#143376 is merged now, so I believe the drop-order-bug-found-by-dianne concern can be resolved. cc @traviscross And I'd be happy to pick up work on documenting |
Before reattempting stabilization, should a call for testing period be conducted to invite contributors to explicitly try to break this feature? Especially with a focus on weird (or even outright wrong) drop order interactions, given the near miss. |
Yes, if this is what we can easily do and have a lot of wishing to test this out then why not About the documentation part, @dianne, yes you absolutely can open your own PR for documentation (I will close mine accordigly after that), but you might want to check mine if there anything valuable you can reuse (for examples syntax is correct) As for this PR I could maintain this until October, hopefully we will get somewhere until this moment And, feel free to ask anything to help when you will work on |
This code compiles. Is it supposed to? #![feature(if_let_guard)]
struct One(Two);
struct Two(Box<i32>);
fn main() {
let a = One(Two(Box::new(1)));
println!("{:p}", &a);
match a {
One(x) if let Two(ref y) = x => {
// Using y here causes a compile error
println!("{:p}", &x);
}
_ => {}
}
} |
I'd argue yes: #![feature(if_let_guard)]
struct One(Two);
struct Two(Box<i32>);
fn main() {
let a = One(Two(Box::new(1)));
println!("{:p}", &a);
match a {
One(x) if let Two(ref y) = x
// `y` is `&a.0` before `a.0` is moved into `x`
&& { println!("{y:p}"); true } =>
{
// Using y here causes a compile error
println!("{:p}", &x);
}
_ => {}
}
} This is in essence the same as a guard like |
This isn't necessarily wrong, but I think users will find this behavior surprising: #![feature(if_let_guard)]
fn main() {
match 1 {
mut x if let y = &x => {
x = 2;
// prints 1
println!("{y}");
}
_ => {}
}
} |
That is a bit funny.. it's consistent with stable non- For comparison, a normal guard that also exhibits this, which I'd want to behave the same as your fn main() {
let y;
match 1 {
mut x if { y = &x; true } => {
x = 2;
// prints 1
println!("{y}");
}
_ => {}
}
} |
This compiles. Is it supposed to? #![feature(if_let_guard)]
struct Thing;
fn foo() -> &'static Thing {
match Thing {
x if let y = &x => {
{
// This should drop `x`, invalidating `y`.
let a = x;
}
y
}
_ => {
panic!();
}
}
} See also #144939, which has similar behavior. Edit: This seems to have similar behavior on normal |
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
let chains
insideif let
guard is the sameif let
guard temporaries and bindings #143376Related:
if let
guards documentation reference#1823