diff --git a/src/destructors.md b/src/destructors.md index dbf1dcd1d..b9305b201 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -126,6 +126,15 @@ r[destructors.scope.nesting.match-arm] * The parent of the expression after the `=>` in a `match` expression is the scope of the arm that it's in. +r[destructors.scope.nesting.match-guard-pattern] +* The parent of an `if let` guard pattern is the scope of the guard expression. + +r[destructors.scope.nesting.match-guard-bindings] +* Variables bound by `if let` guard patterns have their drop scope determined by + guard success: + - On guard failure: dropped immediately after guard evaluation + - On guard success: scope extends to the end of the match arm body + r[destructors.scope.nesting.match] * The parent of the arm scope is the scope of the `match` expression that it belongs to. @@ -204,8 +213,8 @@ smallest scope that contains the expression and is one of the following: * A statement. * The body of an [`if`], [`while`] or [`loop`] expression. * The `else` block of an `if` expression. -* The non-pattern matching condition expression of an `if` or `while` expression, - or a `match` guard. +* The condition expression of an `if` or `while` expression. +* A `match` guard expression, including `if let` guard patterns. * The body expression for a match arm. * Each operand of a [lazy boolean expression]. * The pattern-matching condition(s) and consequent body of [`if`] ([destructors.scope.temporary.edition2024]). @@ -415,6 +424,58 @@ let x = (&temp()).use_temp(); // ERROR # x; ``` +r[destructors.scope.match-guards] +### Match Guards and Pattern Binding + +r[destructors.scope.match-guards.basic] +Match guard expressions create their own temporary scope. Variables bound within +guard patterns have conditional drop scopes based on guard evaluation results. + +r[destructors.scope.match-guards.if-let] +For `if let` guards specifically: + +1. **Guard pattern evaluation**: The `if let` pattern is evaluated within the + guard's temporary scope. +2. **Conditional binding scope**: + - If the pattern matches, bound variables extend their scope to the match arm body + - If the pattern fails, bound variables are dropped immediately +3. **Temporary cleanup**: Temporaries created during guard evaluation are dropped + when the guard scope ends, regardless of pattern match success. + +r[destructors.scope.match-guards.multiple] +For multiple guards connected by `&&`: +- Each guard maintains its own temporary scope +- Failed guards drop their bindings before subsequent guard evaluation +- Only successful guard bindings are available in the arm body +- Guards are evaluated left-to-right with short-circuit semantics + +```rust +# struct PrintOnDrop(&'static str); +# impl Drop for PrintOnDrop { +# fn drop(&mut self) { +# println!("drop({})", self.0); +# } +# } +# fn expensive_operation(x: i32) -> Option { +# Some(PrintOnDrop("expensive result")) +# } + +match Some(42) { + // Guard creates temporary scope for pattern evaluation + Some(x) if let Some(y) = expensive_operation(x) => { + // Both x (from main pattern) and y (from guard) are live + // y will be dropped at end of this arm + println!("Success case"); + } // y dropped here + Some(x) => { + // If guard failed, y was already dropped during guard evaluation + // expensive_operation result was also dropped + println!("Guard failed case"); + } + None => {} +} +``` + r[destructors.forget] ## Not running destructors diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 5bfbbc76d..9694b4fb8 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -9,7 +9,7 @@ MatchExpression -> MatchArms? `}` -Scrutinee -> Expression _except [StructExpression]_ +Scrutinee -> Expression _except_ [StructExpression] MatchArms -> ( MatchArm `=>` ( ExpressionWithoutBlock `,` | ExpressionWithBlock `,`? ) )* @@ -17,7 +17,27 @@ MatchArms -> MatchArm -> OuterAttribute* Pattern MatchArmGuard? -MatchArmGuard -> `if` Expression +MatchArmGuard -> `if` MatchConditions + +MatchConditions -> + Expression + | MatchGuardChain + +MatchGuardChain -> MatchGuardCondition ( `&&` MatchGuardCondition )* + +MatchGuardCondition -> + Expression _except [ExcludedMatchConditions]_ + | OuterAttribute* `let` Pattern `=` MatchGuardScrutinee + +MatchGuardScrutinee -> Expression _except [ExcludedMatchConditions]_ + +@root ExcludedMatchConditions -> + LazyBooleanExpression + | RangeExpr + | RangeFromExpr + | RangeInclusiveExpr + | AssignmentExpression + | CompoundAssignmentExpression ``` @@ -102,12 +122,11 @@ r[expr.match.guard] r[expr.match.guard.intro] Match arms can accept _match guards_ to further refine the criteria for matching a case. -r[expr.match.guard.type] -Pattern guards appear after the pattern and consist of a `bool`-typed expression following the `if` keyword. +r[expr.match.guard.condition] +Pattern guards appear after the pattern following the `if` keyword and consist of an [Expression] with a [boolean type][type.bool] or a conditional `let` match. r[expr.match.guard.behavior] -When the pattern matches successfully, the pattern guard expression is executed. -If the expression evaluates to true, the pattern is successfully matched against. +When the pattern matches successfully, the pattern guard is executed. If all of the guard condition operands evaluate to `true` and all of the `let` patterns successfully match their [scrutinee]s, the match arm is successfully matched against and the arm body is executed. r[expr.match.guard.next] Otherwise, the next pattern, including other matches with the `|` operator in the same arm, is tested. @@ -144,12 +163,75 @@ Before evaluating the guard, a shared reference is taken to the part of the scru While evaluating the guard, this shared reference is then used when accessing the variable. r[expr.match.guard.value] -Only when the guard evaluates to true is the value moved, or copied, from the scrutinee into the variable. +Only when the guard evaluates successfully is the value moved, or copied, from the scrutinee into the variable. This allows shared borrows to be used inside guards without moving out of the scrutinee in case guard fails to match. r[expr.match.guard.no-mutation] Moreover, by holding a shared reference while evaluating the guard, mutation inside guards is also prevented. +r[expr.match.guard.let] +Guards can use `let` patterns to conditionally match a scrutinee and to bind new variables into scope when the pattern matches successfully. + +> [!EXAMPLE] +> In this example, the guard condition `let Some(first_char) = name.chars().next()` is evaluated. If the `if let` expression successfully matches (i.e., the string has at least one character), the arm's body is executed with both `name` and `first_char` available. Otherwise, pattern matching continues to the next arm. +> +> The key point is that the `if let` guard creates a new binding (`first_char`) that's only available if the guard succeeds, and this binding can be used alongside the original pattern bindings (`name`) in the arm's body. +> ```rust +> # enum Command { +> # Run(String), +> # Stop, +> # } +> let cmd = Command::Run("example".to_string()); +> +> match cmd { +> Command::Run(name) if let Some(first_char) = name.chars().next() => { +> // Both `name` and `first_char` are available here +> println!("Running: {name} (starts with '{first_char}')"); +> } +> Command::Run(name) => { +> println!("{name} is empty"); +> } +> _ => {} +> } +> ``` + +r[expr.match.guard.chains] +## Match guard chains + +r[expr.match.guard.chains.intro] +Multiple guard condition operands can be separated with `&&`. + +> [!EXAMPLE] +> ```rust +> # let foo = Some([123]); +> # let already_checked = false; +> match foo { +> Some(xs) if let [single] = xs && !already_checked => { dbg!(single); } +> _ => {} +> } +> ``` + +r[expr.match.guard.chains.order] +Similar to a `&&` [LazyBooleanExpression], each operand is evaluated from left-to-right until an operand evaluates as `false` or a `let` match fails, in which case the subsequent operands are not evaluated. + +r[expr.match.guard.chains.bindings] +The bindings of each `let` pattern are put into scope to be available for the next condition operand and the match arm body. + +r[expr.match.guard.chains.or] +If any guard condition operand is a `let` pattern, then none of the condition operands can be a `||` [lazy boolean operator expression][expr.bool-logic] due to ambiguity and precedence with the `let` scrutinee. + +> [!EXAMPLE] +> If a `||` expression is needed, then parentheses can be used. For example: +> +> ```rust +> # let foo = Some(123); +> match foo { +> // Parentheses are required here. +> Some(x) if (x < -100 || x > 20) => {} +> _ => {} +> } +> ``` + r[expr.match.attributes] ## Attributes on match arms @@ -171,3 +253,4 @@ r[expr.match.attributes.inner] [Range Pattern]: ../patterns.md#range-patterns [scrutinee]: ../glossary.md#scrutinee [value expression]: ../expressions.md#place-expressions-and-value-expressions +[scope and drop section]: ../destructors.md#match-guards-and-pattern-binding diff --git a/src/names/scopes.md b/src/names/scopes.md index 0a7240512..d8f253a1e 100644 --- a/src/names/scopes.md +++ b/src/names/scopes.md @@ -54,6 +54,8 @@ r[names.scopes.pattern-bindings.let-chains] * [`if let`] and [`while let`] bindings are valid in the following conditions as well as the consequent block. r[names.scopes.pattern-bindings.match-arm] * [`match` arms] bindings are within the [match guard] and the match arm expression. +r[names.scopes.pattern-bindings.match-guard-let] +* [`match` guard `let`] bindings are valid in the following guard conditions and the match arm expression if the guard succeeds. r[names.scopes.pattern-bindings.items] Local variable scopes do not extend into item declarations. @@ -347,6 +349,7 @@ impl ImplExample { [`macro_use` prelude]: preludes.md#macro_use-prelude [`macro_use`]: ../macros-by-example.md#the-macro_use-attribute [`match` arms]: ../expressions/match-expr.md +[`match` guard `let`]: expr.match.guard.let [`Self`]: ../paths.md#self-1 [Associated consts]: ../items/associated-items.md#associated-constants [associated items]: ../items/associated-items.md