From 19de488bde270f1e336eb7fca07336ef7b5353a5 Mon Sep 17 00:00:00 2001 From: Kivooeo Date: Fri, 16 May 2025 20:31:31 +0500 Subject: [PATCH 1/3] added documentation for if let --- src/expressions/match-expr.md | 103 +++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 5bfbbc76d..90bdda5f5 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -9,15 +9,25 @@ MatchExpression -> MatchArms? `}` -Scrutinee -> Expression _except [StructExpression]_ +Scrutinee -> + Expression _except_ [StructExpression] MatchArms -> ( MatchArm `=>` ( ExpressionWithoutBlock `,` | ExpressionWithBlock `,`? ) )* MatchArm `=>` Expression `,`? -MatchArm -> OuterAttribute* Pattern MatchArmGuard? +MatchArm -> + OuterAttribute* Pattern MatchArmGuard? -MatchArmGuard -> `if` Expression +MatchArmGuard -> + `if` MatchConditions + +MatchConditions -> + MatchCondition ( `&&` MatchCondition )* + +MatchCondition -> + OuterAttribute* `let` Pattern `=` Scrutinee + | Expression ``` @@ -150,6 +160,93 @@ This allows shared borrows to be used inside guards without moving out of the sc 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.if.let.guard] +## If Let Guards +Match arms can include `if let` guards to allow conditional pattern matching within the guard clause. + +r[expr.match.if.let.guard.syntax] +```rust,ignore +match expression { + pattern if let subpattern = guard_expr => arm_body, + ... +} +``` +Here, `guard_expr` is evaluated and matched against `subpattern`. If the `if let` expression in the guard matches successfully, the arm's body is executed. Otherwise, pattern matching continues to the next arm. + +r[expr.match.if.let.guard.behavior] +When the pattern matches successfully, the `if let` expression in the guard is evaluated: + * The guard proceeds if the inner pattern (`subpattern`) matches the result of `guard_expr`. + * Otherwise, the next arm is tested. + +```rust,ignore +let value = Some(10); + +let msg = match value { + Some(x) if let Some(y) = Some(x - 1) => format!("Matched inner value: {}", y), + _ => "No match".to_string(), +}; +``` + +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. + +```rust,ignore +let opt = Some(42); + +match opt { + Some(x) if let Some(y) = Some(x + 1) => { + // Both `x` and `y` are available in this arm, + // since the pattern matched and the guard evaluated to true. + println!("x = {}, y = {}", x, y); + } + _ => { + // `y` is not available here --- it was only bound inside the guard above. + // Uncommenting the line below will cause a compile-time error: + // println!("{}", y); // error: cannot find value `y` in this scope + } +} + +// Outside the match expression, neither `x` nor `y` are in scope. +``` + +* The outer pattern variables (`x`) follow the same borrowing behavior as in standard match guards (see below). + +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. + +r[expr.match.if.let.guard.borrowing] +Variables bound by the outer pattern follow the same borrowing rules as standard match guards: +* A shared reference is taken to pattern variables before guard evaluation +* Values are moved or copied only when the guard succeeds +* Moving from outer pattern variables within the guard is restricted + +```rust,ignore +fn take(value: T) -> Option { Some(value) } + +let val = Some(vec![1, 2, 3]); +match val { + Some(v) if let Some(_) = take(v) => "moved", // ERROR: cannot move `v` + Some(v) if let Some(_) = take(v.clone()) => "cloned", // OK + _ => "none", +} +``` +> [!NOTE] +> Multiple matches using the `|` operator can cause the pattern guard and the side effects it has to execute multiple times. For example: +> ```rust,ignore +> use std::cell::Cell; +> +> let i: Cell = Cell::new(0); +> match 1 { +> 1 | _ if let Some(_) = { i.set(i.get() + 1); Some(1) } => {} +> _ => {} +> } +> assert_eq!(i.get(), 2); // Guard is executed twice +> ``` + r[expr.match.attributes] ## Attributes on match arms From b7317ce4c4099a22a70f465c04ac068751d3fda6 Mon Sep 17 00:00:00 2001 From: Kivooeo Date: Tue, 10 Jun 2025 00:30:46 +0500 Subject: [PATCH 2/3] cutted down match-expr, added info in destructors, fixed syntax, removed ingore from some blocks --- src/destructors.md | 65 ++++++++++++++++++++++- src/expressions/match-expr.md | 98 +++++++++++++++-------------------- src/names/scopes.md | 3 ++ 3 files changed, 108 insertions(+), 58 deletions(-) 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 90bdda5f5..f3ea1ca1c 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -23,11 +23,25 @@ MatchArmGuard -> `if` MatchConditions MatchConditions -> - MatchCondition ( `&&` MatchCondition )* + Expression + | MatchConditionChain -MatchCondition -> - OuterAttribute* `let` Pattern `=` Scrutinee - | Expression +MatchConditionChain -> + MatchChainCondition ( `&&` MatchChainCondition )* + +MatchChainCondition -> + Expression _except [ExcludedMatchConditions]_ + | OuterAttribute* `let` Pattern `=` MatchGuardScrutinee + +MatchGuardScrutinee -> Expression _except [ExcludedMatchConditions]_ + +@root ExcludedMatchConditions -> + LazyBooleanExpression + | RangeExpr + | RangeFromExpr + | RangeInclusiveExpr + | AssignmentExpression + | CompoundAssignmentExpression ``` @@ -161,71 +175,42 @@ 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.if.let.guard] -## If Let Guards -Match arms can include `if let` guards to allow conditional pattern matching within the guard clause. +## If-let Guards +Match arms can have additional conditions using *if-let guards*. These allow using `if let` expressions within match guard clauses, enabling conditional pattern matching directly in the guard. r[expr.match.if.let.guard.syntax] -```rust,ignore -match expression { - pattern if let subpattern = guard_expr => arm_body, - ... +```rust +enum Command { + Run(String), + Stop, } -``` -Here, `guard_expr` is evaluated and matched against `subpattern`. If the `if let` expression in the guard matches successfully, the arm's body is executed. Otherwise, pattern matching continues to the next arm. - -r[expr.match.if.let.guard.behavior] -When the pattern matches successfully, the `if let` expression in the guard is evaluated: - * The guard proceeds if the inner pattern (`subpattern`) matches the result of `guard_expr`. - * Otherwise, the next arm is tested. - -```rust,ignore -let value = Some(10); - -let msg = match value { - Some(x) if let Some(y) = Some(x - 1) => format!("Matched inner value: {}", y), - _ => "No match".to_string(), -}; -``` - -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. -```rust,ignore -let opt = Some(42); - -match opt { - Some(x) if let Some(y) = Some(x + 1) => { - // Both `x` and `y` are available in this arm, - // since the pattern matched and the guard evaluated to true. - println!("x = {}, y = {}", x, y); +match cmd { + Command::Run(name) if let Some(first_char) = name.chars().next() => { + // Both `name` and `first_char` are available here + println!("Running: {} (starts with '{}')", name, first_char); } - _ => { - // `y` is not available here --- it was only bound inside the guard above. - // Uncommenting the line below will cause a compile-time error: - // println!("{}", y); // error: cannot find value `y` in this scope + Command::Run(name) => { + println!("Cannot run command: {}", name); } + _ => {} } - -// Outside the match expression, neither `x` nor `y` are in scope. ``` +Here, 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 outer pattern variables (`x`) follow the same borrowing behavior as in standard match guards (see below). +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. -r[expr.match.if.let.guard.drop] - -* Variables bound inside `if let` guards are dropped before evaluating subsequent match arms. +r[expr.match.if.let.guard.behavior] +When the pattern matches successfully, the `if let` expression in the guard is evaluated: + * The guard proceeds if the inner pattern (`subpattern`) matches the result of `guard_expr`. + * Otherwise, the next arm is tested. -* Temporaries created during guard evaluation follow standard drop semantics and are cleaned up appropriately. +r[expr.match.if.let.guard.scope] -r[expr.match.if.let.guard.borrowing] -Variables bound by the outer pattern follow the same borrowing rules as standard match guards: -* A shared reference is taken to pattern variables before guard evaluation -* Values are moved or copied only when the guard succeeds -* Moving from outer pattern variables within the guard is restricted +For detailed information about variable scope and drop behavior, see the [scope and drop section]. ```rust,ignore -fn take(value: T) -> Option { Some(value) } +# fn take(value: T) -> Option { Some(value) } let val = Some(vec![1, 2, 3]); match val { @@ -236,7 +221,7 @@ match val { ``` > [!NOTE] > Multiple matches using the `|` operator can cause the pattern guard and the side effects it has to execute multiple times. For example: -> ```rust,ignore +> ```rust > use std::cell::Cell; > > let i: Cell = Cell::new(0); @@ -268,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..086d3aaae 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-if-let] +* [`if let` guard] bindings are within the guard expression 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 +[`if let` guard]: ../expressions/match-expr.md#if-let-guards [`Self`]: ../paths.md#self-1 [Associated consts]: ../items/associated-items.md#associated-constants [associated items]: ../items/associated-items.md From 60740c8e55218fa01e99dcff555cad72451f7dfa Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 10 Jun 2025 16:37:27 -0700 Subject: [PATCH 3/3] Rework match guard chain This applies some editorial rework, mainly to match the style of `if` conditions. --- src/expressions/match-expr.md | 120 +++++++++++++++++----------------- src/names/scopes.md | 6 +- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index f3ea1ca1c..9694b4fb8 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -9,27 +9,23 @@ MatchExpression -> MatchArms? `}` -Scrutinee -> - Expression _except_ [StructExpression] +Scrutinee -> Expression _except_ [StructExpression] MatchArms -> ( MatchArm `=>` ( ExpressionWithoutBlock `,` | ExpressionWithBlock `,`? ) )* MatchArm `=>` Expression `,`? -MatchArm -> - OuterAttribute* Pattern MatchArmGuard? +MatchArm -> OuterAttribute* Pattern MatchArmGuard? -MatchArmGuard -> - `if` MatchConditions +MatchArmGuard -> `if` MatchConditions MatchConditions -> Expression - | MatchConditionChain + | MatchGuardChain -MatchConditionChain -> - MatchChainCondition ( `&&` MatchChainCondition )* +MatchGuardChain -> MatchGuardCondition ( `&&` MatchGuardCondition )* -MatchChainCondition -> +MatchGuardCondition -> Expression _except [ExcludedMatchConditions]_ | OuterAttribute* `let` Pattern `=` MatchGuardScrutinee @@ -126,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. @@ -168,68 +163,73 @@ 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.if.let.guard] -## If-let Guards -Match arms can have additional conditions using *if-let guards*. These allow using `if let` expressions within match guard clauses, enabling conditional pattern matching directly in the guard. +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. -r[expr.match.if.let.guard.syntax] -```rust -enum Command { - Run(String), - Stop, -} +> [!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"); +> } +> _ => {} +> } +> ``` -match cmd { - Command::Run(name) if let Some(first_char) = name.chars().next() => { - // Both `name` and `first_char` are available here - println!("Running: {} (starts with '{}')", name, first_char); - } - Command::Run(name) => { - println!("Cannot run command: {}", name); - } - _ => {} -} -``` -Here, 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. +r[expr.match.guard.chains] +## Match guard chains -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. +r[expr.match.guard.chains.intro] +Multiple guard condition operands can be separated with `&&`. -r[expr.match.if.let.guard.behavior] -When the pattern matches successfully, the `if let` expression in the guard is evaluated: - * The guard proceeds if the inner pattern (`subpattern`) matches the result of `guard_expr`. - * Otherwise, the next arm is tested. +> [!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.if.let.guard.scope] +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. -For detailed information about variable scope and drop behavior, see the [scope and drop section]. +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. -```rust,ignore -# fn take(value: T) -> Option { Some(value) } +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. -let val = Some(vec![1, 2, 3]); -match val { - Some(v) if let Some(_) = take(v) => "moved", // ERROR: cannot move `v` - Some(v) if let Some(_) = take(v.clone()) => "cloned", // OK - _ => "none", -} -``` -> [!NOTE] -> Multiple matches using the `|` operator can cause the pattern guard and the side effects it has to execute multiple times. For example: -> ```rust -> use std::cell::Cell; +> [!EXAMPLE] +> If a `||` expression is needed, then parentheses can be used. For example: > -> let i: Cell = Cell::new(0); -> match 1 { -> 1 | _ if let Some(_) = { i.set(i.get() + 1); Some(1) } => {} +> ```rust +> # let foo = Some(123); +> match foo { +> // Parentheses are required here. +> Some(x) if (x < -100 || x > 20) => {} > _ => {} > } -> assert_eq!(i.get(), 2); // Guard is executed twice > ``` r[expr.match.attributes] diff --git a/src/names/scopes.md b/src/names/scopes.md index 086d3aaae..d8f253a1e 100644 --- a/src/names/scopes.md +++ b/src/names/scopes.md @@ -54,8 +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-if-let] -* [`if let` guard] bindings are within the guard expression and the match arm expression if the guard succeeds. +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. @@ -349,7 +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 -[`if let` guard]: ../expressions/match-expr.md#if-let-guards +[`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