Skip to content

Commit e69fef8

Browse files
committed
rework scoping rules for match arms and guards
1 parent 12fd5b7 commit e69fef8

File tree

2 files changed

+57
-71
lines changed

2 files changed

+57
-71
lines changed

src/destructors.md

Lines changed: 57 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,6 @@ r[destructors.scope.nesting.match-arm]
127127
* The parent of the expression after the `=>` in a `match` expression is the
128128
scope of the arm that it's in.
129129

130-
r[destructors.scope.nesting.match-guard-pattern]
131-
* The parent of an `if let` guard pattern is the scope of the guard expression.
132-
133-
r[destructors.scope.nesting.match-guard-bindings]
134-
* Variables bound by `if let` guard patterns have their drop scope determined by
135-
guard success:
136-
- On guard failure: dropped immediately after guard evaluation
137-
- On guard success: scope extends to the end of the match arm body
138-
139130
r[destructors.scope.nesting.match]
140131
* The parent of the arm scope is the scope of the `match` expression that it
141132
belongs to.
@@ -174,11 +165,8 @@ patterns_in_parameters(
174165
r[destructors.scope.bindings]
175166
### Scopes of local variables
176167

177-
r[destructors.scope.bindings.intro]
178-
Local variables declared in a `let` statement are associated to the scope of
179-
the block that contains the `let` statement. Local variables declared in a
180-
`match` expression are associated to the arm scope of the `match` arm that they
181-
are declared in.
168+
r[destructors.scope.bindings.let]
169+
Local variables declared in a `let` statement are associated to the scope of the block that contains the `let` statement.
182170

183171
```rust
184172
# struct PrintOnDrop(&'static str);
@@ -194,6 +182,49 @@ let declared_first = PrintOnDrop("Dropped last in outer scope");
194182
let declared_last = PrintOnDrop("Dropped first in outer scope");
195183
```
196184

185+
r[destructors.scope.bindings.match-arm]
186+
Local variables declared in a `match` expression or pattern-matching `match` guard are associated to the arm scope of the `match` arm that they are declared in.
187+
188+
```rust
189+
# #![allow(irrefutable_let_patterns)]
190+
# struct PrintOnDrop(&'static str);
191+
# impl Drop for PrintOnDrop {
192+
# fn drop(&mut self) {
193+
# println!("drop({})", self.0);
194+
# }
195+
# }
196+
match PrintOnDrop("Dropped last in the first arm's scope") {
197+
// When guard evaluation succeeds, control-flow stays in the arm and
198+
// values may be moved from the scrutinee into the arm's bindings,
199+
// causing them to be dropped in the arm's scope.
200+
x if let y = PrintOnDrop("Dropped second in the first arm's scope")
201+
&& let z = PrintOnDrop("Dropped first in the first arm's scope") =>
202+
{
203+
let declared_in_block = PrintOnDrop("Dropped in inner scope");
204+
// Pattern-matching guards' bindings and temporaries are dropped in
205+
// reverse order, dropping each guard condition operand's bindings
206+
// before its temporaries. Lastly, variables bound by the arm's
207+
// pattern are dropped.
208+
}
209+
_ => unreachable!(),
210+
}
211+
212+
match PrintOnDrop("Dropped in the enclosing temporary scope") {
213+
// When guard evaluation fails, control-flow leaves the arm scope,
214+
// causing bindings and temporaries from earlier pattern-matching
215+
// guard condition operands to be dropped. This occurs before evaluating
216+
// the next arm's guard or body.
217+
_ if let y = PrintOnDrop("Dropped in the first arm's scope")
218+
&& false => unreachable!(),
219+
// When a guard is executed multiple times due to self-overlapping
220+
// or-patterns, control-flow leaves the arm scope when the guard fails
221+
// and re-enters the arm scope before executing the guard again.
222+
_ | _ if let y = PrintOnDrop("Dropped in the second arm's scope twice")
223+
&& false => unreachable!(),
224+
_ => {},
225+
}
226+
```
227+
197228
r[destructors.scope.bindings.patterns]
198229
Variables in patterns are dropped in reverse order of declaration within the pattern.
199230

@@ -264,9 +295,8 @@ smallest scope that contains the expression and is one of the following:
264295
* A statement.
265296
* The body of an [`if`], [`while`] or [`loop`] expression.
266297
* The `else` block of an `if` expression.
267-
* The condition expression of an `if` or `while` expression.
268-
* A `match` guard expression, including `if let` guard patterns.
269-
* The body expression for a match arm.
298+
* The non-pattern matching condition expression of an `if` or `while` expression or a non-pattern-matching `match` [guard condition operand].
299+
* The pattern-matching guard, if present, and body expression for a `match` arm.
270300
* Each operand of a [lazy boolean expression].
271301
* The pattern-matching condition(s) and consequent body of [`if`] ([destructors.scope.temporary.edition2024]).
272302
* The pattern-matching condition and loop body of [`while`].
@@ -326,8 +356,16 @@ while let x = PrintOnDrop("while let scrutinee").0 {
326356
// Scrutinee is dropped at the end of the function, before local variables
327357
// (because this is the tail expression of the function body block).
328358
match PrintOnDrop("Matched value in final expression") {
329-
// Dropped once the condition has been evaluated
359+
// Non-pattern-matching guards' temporaries are dropped once the
360+
// condition has been evaluated
330361
_ if PrintOnDrop("guard condition").0 == "" => (),
362+
// Pattern-matching guards' temporaries are dropped when leaving the
363+
// arm's scope
364+
_ if let "guard scrutinee" = PrintOnDrop("guard scrutinee").0 => {
365+
let _ = &PrintOnDrop("lifetime-extended temporary in inner scope");
366+
// `lifetime-extended temporary in inner scope` is dropped here
367+
}
368+
// `guard scrutinee` is dropped here
331369
_ => (),
332370
}
333371
```
@@ -484,58 +522,6 @@ let x = (&temp()).use_temp(); // ERROR
484522
# x;
485523
```
486524

487-
r[destructors.scope.match-guards]
488-
### Match Guards and Pattern Binding
489-
490-
r[destructors.scope.match-guards.basic]
491-
Match guard expressions create their own temporary scope. Variables bound within
492-
guard patterns have conditional drop scopes based on guard evaluation results.
493-
494-
r[destructors.scope.match-guards.if-let]
495-
For `if let` guards specifically:
496-
497-
1. **Guard pattern evaluation**: The `if let` pattern is evaluated within the
498-
guard's temporary scope.
499-
2. **Conditional binding scope**:
500-
- If the pattern matches, bound variables extend their scope to the match arm body
501-
- If the pattern fails, bound variables are dropped immediately
502-
3. **Temporary cleanup**: Temporaries created during guard evaluation are dropped
503-
when the guard scope ends, regardless of pattern match success.
504-
505-
r[destructors.scope.match-guards.multiple]
506-
For multiple guards connected by `&&`:
507-
- Each guard maintains its own temporary scope
508-
- Failed guards drop their bindings before subsequent guard evaluation
509-
- Only successful guard bindings are available in the arm body
510-
- Guards are evaluated left-to-right with short-circuit semantics
511-
512-
```rust
513-
# struct PrintOnDrop(&'static str);
514-
# impl Drop for PrintOnDrop {
515-
# fn drop(&mut self) {
516-
# println!("drop({})", self.0);
517-
# }
518-
# }
519-
# fn expensive_operation(x: i32) -> Option<PrintOnDrop> {
520-
# Some(PrintOnDrop("expensive result"))
521-
# }
522-
523-
match Some(42) {
524-
// Guard creates temporary scope for pattern evaluation
525-
Some(x) if let Some(y) = expensive_operation(x) => {
526-
// Both x (from main pattern) and y (from guard) are live
527-
// y will be dropped at end of this arm
528-
println!("Success case");
529-
} // y dropped here
530-
Some(x) => {
531-
// If guard failed, y was already dropped during guard evaluation
532-
// expensive_operation result was also dropped
533-
println!("Guard failed case");
534-
}
535-
None => {}
536-
}
537-
```
538-
539525
r[destructors.forget]
540526
## Not running destructors
541527

@@ -563,6 +549,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
563549
[closure]: types/closure.md
564550
[destructors]: destructors.md
565551
[expression]: expressions.md
552+
[guard condition operand]: expressions/match-expr.md#match-guard-chains
566553
[identifier pattern]: patterns.md#identifier-patterns
567554
[initialized]: glossary.md#initialized
568555
[interior mutability]: interior-mutability.md

src/expressions/match-expr.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,3 @@ r[expr.match.attributes.inner]
253253
[Range Pattern]: ../patterns.md#range-patterns
254254
[scrutinee]: ../glossary.md#scrutinee
255255
[value expression]: ../expressions.md#place-expressions-and-value-expressions
256-
[scope and drop section]: ../destructors.md#match-guards-and-pattern-binding

0 commit comments

Comments
 (0)