-
Notifications
You must be signed in to change notification settings - Fork 536
if let
guards documentation
#1823
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
65337a7
to
e704b43
Compare
src/expressions/match-expr.md
Outdated
```rust,ignore | ||
match expression { | ||
pattern if let subpattern = guard_expr => arm_body, | ||
... | ||
} | ||
``` |
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.
Would it be possible to make a working example?
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.
Im not really sure, because it's not stable feature and contains behind #![feature...]
gate that's aren't allowed in documentation and CI will fail with this error
Running target/debug/style-check ../src
error in ../src/expressions/match-expr.md: #![feature] attributes are not allowed
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.
You can just add a full example that will fail CI for now.
src/expressions/match-expr.md
Outdated
* The `if let` guard may refer to variables bound by the outer match pattern. | ||
* New variables bound inside the `if let` guard (e.g., `y` in the example above) are available within the body of the match arm where the guard evaluates to `true`, but are not accessible in other arms or outside the match expression. | ||
|
||
```rust,ignore |
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.
```rust,ignore | |
```rust |
src/expressions/match-expr.md
Outdated
2. Guard evaluation happens after that, and: | ||
* It runs using a shared borrow of the scrutinee | ||
* You cannot move from the scrutinee inside the guard. | ||
* New bindings created inside the guard (e.g., via `if let Some(y) = expr`) are local to the guard and do not persist into the match arm body. |
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.
What does that mean?
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 updated it and it now should make more sense
src/expressions/match-expr.md
Outdated
``` | ||
> [!NOTE] | ||
> Unlike regular if guards, `if let` guards execute only once per match arm, even if the pattern uses the `|` operator to match multiple patterns. This avoids repeated evaluation and potential side effects. | ||
> ```rust,ignore |
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.
> ```rust,ignore | |
> ```rust |
d925c41
to
303dab2
Compare
@WaffleLapkin not sure if you got notifications when i update something, but i update almost all your reviews expect ones that asks to make code runnable, because im not sure if this is possible, we can remove |
…31,fee1-dead Stabilize `if let` guards (`feature(if_let_guard)`) ## Summary This proposes the stabilization of `if let` guards (tracking issue: rust-lang#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) - Tests that temporaries in guards are dropped at the correct time - [`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#140981 - A complicated drop order test involved `let chain` was made by `@est31` ## 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 All blocking issues have been resolved: - [x] - rust-lang#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 --- **Related:** - Tracking Issue: rust-lang#51114 - RFC: rust-lang/rfcs#2294 - Documentation PR: rust-lang/reference#1823
@rustbot label -S-waiting-on-stabilization |
486818c
to
7799977
Compare
src/expressions/match-expr.md
Outdated
|
||
r[expr.match.if.let.guard.scope] | ||
* The `if let` guard may refer to variables bound by the outer match pattern. | ||
* New variables bound inside the `if let` guard (e.g., `y` in the example above) are available within the body of the match arm where the guard evaluates to `true`, but are not accessible in other arms or outside the match expression. |
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.
May be worth being a bit more precise w.r.t drop behavior (i.e. that anything bound in the if let
guards are dropped before evaluating other match arms)?
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, we can add something like this
r[expr.match.if.let.guard.drop]
* Variables bound inside `if let` guards are dropped before evaluating subsequent match arms.
* Temporaries created during guard evaluation follow standard drop semantics and are cleaned up appropriately.
What's your opinion on this?
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.
Drop behavior should be documented in the destructors chapter. I would expect it to be somewhat similar to the changes that were made for if
(such as in destructors.scope.temporary.enclosing).
The stabilization report doesn't explain what the drop order is or how temporaries are handled, so it is difficult for me to evaluate what this should say.
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.
Something like this? I'm just not sure how full should be explanation about drop order
r[destructors.scope.temporary.if-let-guard]
### If Let Guards in Match Expressions
When an `if let` guard is evaluated in a match expression:
1. **Guard evaluation scope**: Variables bound by the `if let` pattern and any temporaries created during guard evaluation have a scope that extends through the match arm body if the guard succeeds.
2. **Failed guard cleanup**: If the `if let` guard fails to match:
- Any variables bound by the guard pattern are immediately dropped
- Temporaries created during guard evaluation are dropped
- Pattern matching continues to the next arm
3. **Successful guard**: If the guard succeeds:
- Guard-bound variables remain live for the duration of the match arm body
- They are dropped at the end of the match arm execution
4. **Multiple guards**: In arms with multiple guards connected by `&&`:
- Each failed guard drops its own bindings before evaluating the next guard
- Only successful guard bindings are available in the arm body
#### Example
...
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.
Later I'm going to do a cleanup pass, as there are a number of changes I want to make.
I'm wondering if it is possible to defer most of this documentation to the if
chapter? It already explains how chains work and such, and it seems like it could just link to that instead of re-explaining it?
Are there changes to names.scopes.pattern-bindings needed to define the scopes of these bindings?
src/expressions/match-expr.md
Outdated
MatchArmGuard -> | ||
`if` MatchConditions | ||
|
||
MatchConditions -> | ||
MatchCondition ( `&&` MatchCondition )* | ||
|
||
MatchCondition -> | ||
OuterAttribute* `let` Pattern `=` Scrutinee | ||
| Expression |
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 seems to be missing some things.
Similar to if
expressions, this will need two separate rules. Something like Expression | MatchConditions
because without a let
it is allowed to have LazyBooleanExpression. Then, MatchConditions
will need to exclude LazyBooleanExpression.
I don't think this should use Scrutinee, because that rejects StructExpression, but that is allowed here.
This will also need similar precedence restrictions as if let
has with restricting LazyBooleanExpression | RangeExpr | RangeFromExpr | RangeInclusiveExpr | AssignmentExpression | CompoundAssignmentExpression
.
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 guess, something simple like this should work right? I just not sure if I can reuse LetChain like this
MatchArmGuard ->
`if` MatchConditions
MatchConditions ->
Expression _except [StructExpression]_
| LetChain
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.
No, because LetChain excludes StructExpression, but we don't want to exclude that here.
We also don't want MatchConditions to exclude StructExpression.
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.
Ok, it's little bit hard to write a working syntax to cover all cases, let's try something like this as a start
MatchArmGuard ->
`if` MatchConditions
MatchConditions ->
MatchCondition ( `&&` MatchCondition )*
MatchCondition ->
OuterAttribute* `let` Pattern `=` Expression _except [ExcludedConditions]_
| Expression _except [ExcludedConditions]_
@root ExcludedConditions ->
LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
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 there needs to be a separate case for a chain vs not-chain.
For example:
match () {
_ if x || y => {} // OK
_ if x || y && let _ = () => {} // ERROR
_ => {}
}
so I think what it needs is something like:
MatchConditions ->
Expression
| MatchConditionChain
MatchConditionChain ->
MatchChainCondition ( `&&` MatchChainCondition )*
MatchChainCondition ->
Expression _except [ExcludedMatchConditions]_
| OuterAttribute* `let` Pattern `=` MatchGuardScrutinee
MatchGuardScrutinee -> Expression _except [ExcludedMatchConditions]_
@root ExcludedMatchConditions ->
LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
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.
well, yeah, separate MatchGuardScrutinee
is a good idea
src/expressions/match-expr.md
Outdated
```rust,ignore | ||
match expression { | ||
pattern if let subpattern = guard_expr => arm_body, | ||
... | ||
} | ||
``` |
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.
You can just add a full example that will fail CI for now.
src/expressions/match-expr.md
Outdated
|
||
r[expr.match.if.let.guard.scope] | ||
* The `if let` guard may refer to variables bound by the outer match pattern. | ||
* New variables bound inside the `if let` guard (e.g., `y` in the example above) are available within the body of the match arm where the guard evaluates to `true`, but are not accessible in other arms or outside the match expression. |
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.
Drop behavior should be documented in the destructors chapter. I would expect it to be somewhat similar to the changes that were made for if
(such as in destructors.scope.temporary.enclosing).
The stabilization report doesn't explain what the drop order is or how temporaries are handled, so it is difficult for me to evaluate what this should say.
…ved ingore from some blocks
@ehuss check last commit, if this any better, i will check why ci fails in code style later today |
It’s been a while since the original PR for
if let
guards was opened, so I decided to take the initiative and push it a bit closer to stabilizationThis PR adds some documentation — I did my best to keep it consistent with the existing docs on
if
guardsThere might still be some rough edges or small inaccuracies, so feel free to point them out
It’s also my first time contributing to the docs, so any feedback is appreciated!
Tracking issue rust-lang/rust#51114
Stabilization: rust-lang/rust#141295
rendered