specify if let guards with updated scoping rules#1957
specify if let guards with updated scoping rules#1957ehuss merged 6 commits intorust-lang:masterfrom
if let guards with updated scoping rules#1957Conversation
7eb684c to
9add291
Compare
| or a `match` guard. | ||
| * The body expression for a match arm. | ||
| * The non-pattern matching condition expression of an `if` or `while` expression or a non-pattern-matching `match` guard condition operand. | ||
| * The pattern-matching guard, if present, and body expression for a `match` arm. |
There was a problem hiding this comment.
To address #1823 (comment) (cc @theemathas), the temporary lifetime of an if let guard scrutinee is always extended, like for if let expressions. There aren't lifetime extension rules like there are for let statements. This rule should hopefully cover that (similar to how the analogous rule for "The pattern-matching condition(s) and consequent body of if" does below). There's an example further down too showing that the scrutinee lives until the end of the arm. Do you think this is sufficient, or would this benefit from additional clarification?
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Oh, are you saying that both the guard and the body expression together count as a single temporary scope (and not two)? I find that unintuitive, but I suppose that works.
There was a problem hiding this comment.
There's a single temporary scope for the arm, which encompasses the guard and body expression, so that if let scrutinees' temporaries live through the body, yes; if let scrutinees aren't temporary destruction scopes by default. Maybe it would be clearer to phrase the temporary scope as being as for the whole arm, with a clarifying note that it includes the guard. Something like "* The arm of a match expression. This includes the arm's guard, if present."
Personally, I'd like stricter scoping rules for if let, but this is consistent with if let/while let expressions. Making their scrutinees be temporary drop scopes by default with lifetime extension rules (like let statements have) would be a larger change requiring an edition break.
|
Is this waiting on anything in particular? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
It looks like my removal of the "Match Guards and Pattern Binding" section was undone? I've re-removed it. I wanted to leave commits before my own mostly untouched, but I can clean up the commit history to remove anything that I'm not preserving in this PR if that'd help keep what's actually meant to be here clearer. |
b090107 to
ab50634
Compare
This comment has been minimized.
This comment has been minimized.
|
Sorry about that @dianne! I had double-checked the reflog when I was fixing that, and I'm not sure how it got missed. |
c56a2ee to
20e729c
Compare
src/expressions/match-expr.md
Outdated
| > # let foo = Some(123); | ||
| > match foo { | ||
| > // Parentheses are required here. | ||
| > Some(x) if (x < -100 || x > 20) => {} |
There was a problem hiding this comment.
As this doesn't have a let, no parens are required here. I suppose you meant to add a let.
There was a problem hiding this comment.
Nice catch; I'd missed that. I've made it look a bit more like the example above it: (diff)
Stabilize `if let` guards (`feature(if_let_guard)`) ## Summary This proposes the stabilization of `if let` guards (tracking issue: #51114, RFC: rust-lang/rfcs#2294). This feature allows `if 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: ```rust enum Command { Run(String), Stop, Pause, } fn process_command(cmd: Command, state: &mut String) { match cmd { Command::Run(name) if let Some(first_char) = name.chars().next() && first_char.is_ascii_alphabetic() => { // Both `name` and `first_char` are available here println!("Running command: {} (starts with '{}')", name, first_char); state.push_str(&format!("Running {}", name)); } Command::Run(name) => { println!("Cannot run command '{}'. Invalid name.", name); } Command::Stop if state.contains("running") => { println!("Stopping current process."); state.clear(); } _ => { println!("Unhandled command or state."); } } } ``` ## 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 nested `if let` statements within match arms: ```rust // Without if let guards match value { Some(x) => { if let Ok(y) = compute(x) { // Both `x` and `y` are available here println!("{}, {}", x, y); } } _ => {} } // With if let guards match value { Some(x) if let Ok(y) = compute(x) => { // Both `x` and `y` are available here println!("{}, {}", x, y); } _ => {} } ``` ## Implementation and Testing The feature has been implemented and tested comprehensively across different scenarios: ### Core Functionality Tests **Scoping and variable binding:** - [`scope.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/scope.rs) - Verifies that bindings created in `if let` guards are properly scoped and available in match arms - [`shadowing.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/shadowing.rs) - Tests that variable shadowing works correctly within guards - [`scoping-consistency.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/scoping-consistency.rs) - Ensures temporaries in guards remain valid for the duration of their match arms **Type system integration:** - [`type-inference.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/type-inference.rs) - Confirms type inference works correctly in `if let` guards - [`typeck.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/typeck.rs) - Verifies type mismatches are caught appropriately **Pattern matching semantics:** - [`exhaustive.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/exhaustive.rs) - Validates that `if let` guards are correctly handled in exhaustiveness analysis - [`move-guard-if-let.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let.rs) and [`move-guard-if-let-chain.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.rs) - Test that conditional moves in guards are tracked correctly by the borrow checker ### Error Handling and Diagnostics - [`warns.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/warns.rs) - Tests warnings for irrefutable patterns and unreachable code in guards - [`parens.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/parens.rs) - Ensures parentheses around `let` expressions are properly rejected - [`macro-expanded.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/macro-expanded.rs) - Verifies macro expansions that produce invalid constructs are caught - [`guard-mutability-2.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/guard-mutability-2.rs) - Tests mutability and ownership violations in guards - [`ast-validate-guards.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2497-if-let-chains/ast-validate-guards.rs) - Validates AST-level syntax restrictions ### Drop Order and Temporaries **Key insight:** Unlike `let_chains` in regular `if` expressions, `if let` guards do not have drop order inconsistencies because: 1. Match guards are clearly scoped to their arms 2. There is no "else block" equivalent that could cause temporal confusion - [`drop-order.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/drop-order.rs) - Check drop order of temporaries create in match guards - [`compare-drop-order.rs`](https://github.com/rust-lang/rust/blob/aef3f5fdf052fbbc16e174aef5da6d50832ca316/tests/ui/rfcs/rfc-2294-if-let-guard/compare-drop-order.rs) - Compares drop order between `if let` guards and nested `if let` in match arms, confirming they behave identically across all editions - #140981 - A complicated drop order test involved `let chain` was made by @est31 - [`drop-order-comparisons-let-chains.rs`](https://github.com/rust-lang/rust/blob/902b4d28783e03e231d8513082cc30c4fcce5d95/tests/ui/drop/drop-order-comparisons-let-chains.rs) - Compares drop order between `let chains` in `if let guard` and regular `if` expressions - [`if-let-guards.rs`](https://github.com/rust-lang/rust/blob/5650d716e0589e2e145ce9027f35bd534e5f862a/tests/ui/drop/if-let-guards.rs) - Test correctness of drop order for bindings and temporaries - [`if-let-guards-2`](https://github.com/rust-lang/rust/blob/3a6c8c8f3d7ae654fdb6ce1255182bda21680655/tests/ui/drop/if-let-guards-2.rs) - The same test as above but more comprehensive and tests more interactions between different features and their drop order, checking that drop order is correct, created by @traviscross ## Edition Compatibility This feature stabilizes on all editions, unlike `let chains` which was limited to edition 2024. This is safe because: 1. `if let` guards don't suffer from the drop order issues that affected `let chains` in regular `if` expressions 2. The scoping is unambiguous - guards are clearly tied to their match arms 3. Extensive testing confirms identical behavior across all editions ## Interactions 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 - [x] - #140981 - [x] - added tests description by @jieyouxu request - [x] - Concers from @scottmcm about stabilizing this across all editions - [x] - check if drop order in all edition when using `let chains` inside `if let` guard is the same - [x] - interactions with guard patters - [x] - pattern bindings drops before guard bindings #143376 - [x] - documentaion (rust-lang/reference#1957) - [ ] (non-blocking) add tests for [this](#145237) and [this](#141295 (comment)) --- **Related:** - Tracking Issue: #51114 - RFC: rust-lang/rfcs#2294 - Documentation PR: rust-lang/reference#1957
Stabilize `if let` guards (`feature(if_let_guard)`) ## Summary This proposes the stabilization of `if let` guards (tracking issue: rust-lang/rust#51114, RFC: rust-lang/rfcs#2294). This feature allows `if 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: ```rust enum Command { Run(String), Stop, Pause, } fn process_command(cmd: Command, state: &mut String) { match cmd { Command::Run(name) if let Some(first_char) = name.chars().next() && first_char.is_ascii_alphabetic() => { // Both `name` and `first_char` are available here println!("Running command: {} (starts with '{}')", name, first_char); state.push_str(&format!("Running {}", name)); } Command::Run(name) => { println!("Cannot run command '{}'. Invalid name.", name); } Command::Stop if state.contains("running") => { println!("Stopping current process."); state.clear(); } _ => { println!("Unhandled command or state."); } } } ``` ## 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 nested `if let` statements within match arms: ```rust // Without if let guards match value { Some(x) => { if let Ok(y) = compute(x) { // Both `x` and `y` are available here println!("{}, {}", x, y); } } _ => {} } // With if let guards match value { Some(x) if let Ok(y) = compute(x) => { // Both `x` and `y` are available here println!("{}, {}", x, y); } _ => {} } ``` ## Implementation and Testing The feature has been implemented and tested comprehensively across different scenarios: ### Core Functionality Tests **Scoping and variable binding:** - [`scope.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/scope.rs) - Verifies that bindings created in `if let` guards are properly scoped and available in match arms - [`shadowing.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/shadowing.rs) - Tests that variable shadowing works correctly within guards - [`scoping-consistency.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/scoping-consistency.rs) - Ensures temporaries in guards remain valid for the duration of their match arms **Type system integration:** - [`type-inference.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/type-inference.rs) - Confirms type inference works correctly in `if let` guards - [`typeck.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/typeck.rs) - Verifies type mismatches are caught appropriately **Pattern matching semantics:** - [`exhaustive.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/exhaustive.rs) - Validates that `if let` guards are correctly handled in exhaustiveness analysis - [`move-guard-if-let.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let.rs) and [`move-guard-if-let-chain.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.rs) - Test that conditional moves in guards are tracked correctly by the borrow checker ### Error Handling and Diagnostics - [`warns.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/warns.rs) - Tests warnings for irrefutable patterns and unreachable code in guards - [`parens.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/parens.rs) - Ensures parentheses around `let` expressions are properly rejected - [`macro-expanded.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/macro-expanded.rs) - Verifies macro expansions that produce invalid constructs are caught - [`guard-mutability-2.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/guard-mutability-2.rs) - Tests mutability and ownership violations in guards - [`ast-validate-guards.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2497-if-let-chains/ast-validate-guards.rs) - Validates AST-level syntax restrictions ### Drop Order and Temporaries **Key insight:** Unlike `let_chains` in regular `if` expressions, `if let` guards do not have drop order inconsistencies because: 1. Match guards are clearly scoped to their arms 2. There is no "else block" equivalent that could cause temporal confusion - [`drop-order.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/drop-order.rs) - Check drop order of temporaries create in match guards - [`compare-drop-order.rs`](https://github.com/rust-lang/rust/blob/aef3f5fdf052fbbc16e174aef5da6d50832ca316/tests/ui/rfcs/rfc-2294-if-let-guard/compare-drop-order.rs) - Compares drop order between `if let` guards and nested `if let` in match arms, confirming they behave identically across all editions - rust-lang/rust#140981 - A complicated drop order test involved `let chain` was made by @est31 - [`drop-order-comparisons-let-chains.rs`](https://github.com/rust-lang/rust/blob/902b4d28783e03e231d8513082cc30c4fcce5d95/tests/ui/drop/drop-order-comparisons-let-chains.rs) - Compares drop order between `let chains` in `if let guard` and regular `if` expressions - [`if-let-guards.rs`](https://github.com/rust-lang/rust/blob/5650d716e0589e2e145ce9027f35bd534e5f862a/tests/ui/drop/if-let-guards.rs) - Test correctness of drop order for bindings and temporaries - [`if-let-guards-2`](https://github.com/rust-lang/rust/blob/3a6c8c8f3d7ae654fdb6ce1255182bda21680655/tests/ui/drop/if-let-guards-2.rs) - The same test as above but more comprehensive and tests more interactions between different features and their drop order, checking that drop order is correct, created by @traviscross ## Edition Compatibility This feature stabilizes on all editions, unlike `let chains` which was limited to edition 2024. This is safe because: 1. `if let` guards don't suffer from the drop order issues that affected `let chains` in regular `if` expressions 2. The scoping is unambiguous - guards are clearly tied to their match arms 3. Extensive testing confirms identical behavior across all editions ## Interactions 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 - [x] - rust-lang/rust#140981 - [x] - added tests description by @jieyouxu request - [x] - Concers from @scottmcm about stabilizing this across all editions - [x] - check if drop order in all edition when using `let chains` inside `if let` guard is the same - [x] - interactions with guard patters - [x] - pattern bindings drops before guard bindings rust-lang/rust#143376 - [x] - documentaion (rust-lang/reference#1957) - [ ] (non-blocking) add tests for [this](rust-lang/rust#145237) and [this](rust-lang/rust#141295 (comment)) --- **Related:** - Tracking Issue: rust-lang/rust#51114 - RFC: rust-lang/rfcs#2294 - Documentation PR: rust-lang/reference#1957
…ved ingore from some blocks
This applies some editorial rework, mainly to match the style of `if` conditions.
- Removes specification of `if let` guards' bindings "only" being valid if the guard succeeds. The arm body is only executed if the guard succeeds, so the bindings are always usable within the arm body when it's executed. - Makes terminology slightly more consistent (replaces some remaining uses of "`if let` guard" with "`let` pattern") - Other small wording and punctuation tweaks.
In our PEG-style grammar, the order matters, and in this case, we need `MatchGuardChain` to come after `Expression`. Thanks-to: Eric Huss <eric@huss.org>
|
This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
ehuss
left a comment
There was a problem hiding this comment.
Thanks! Merging now that rust-lang/rust#141295 has merged.
This is based on #1823 by @Kivooeo and @ehuss, rebased to resolve conflicts. Per rust-lang/rust#141295 (comment), I'm opening this as a separate PR to contribute an updated specification of
if letguards' drop-scoping rules. The new spec is based on the compiler's implementation1, accounting for the changes in rust-lang/rust#143376 and additional corner-cases in examples. I've also done some minor edits to the sections on lexical scope and match expressions, but for the most part they're the same as in #1823.Tracking issue: rust-lang/rust#51114
Stabilization: rust-lang/rust#141295
Footnotes
As with the rules for other syntactic constructs (e.g.
letstatements andifexpressions), this is slightly simplified. Due to implementation details, the compiler's notion of drop scopes is more fine-grained than is necessary for the language spec. ↩