Skip to content

Commit 7cedaea

Browse files
committed
rework scoping rules for match arms and guards
1 parent 2a39499 commit 7cedaea

File tree

2 files changed

+50
-71
lines changed

2 files changed

+50
-71
lines changed

src/destructors.md

Lines changed: 50 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,43 @@ 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+
match PrintOnDrop("Dropped last in the first arm's scope") {
191+
// When guard evaluation succeeds, control-flow stays in the arm and
192+
// values may be moved from the scrutinee into the arm's bindings,
193+
// causing them to be dropped in the arm's scope.
194+
x if let y = PrintOnDrop("Dropped second in the first arm's scope")
195+
&& let z = PrintOnDrop("Dropped first in the first arm's scope") =>
196+
{
197+
let declared_in_block = PrintOnDrop("Dropped in inner scope");
198+
// Pattern-matching guards' bindings and temporaries are dropped in
199+
// reverse order, dropping each guard condition operand's bindings
200+
// before its temporaries. Lastly, variables bound by the arm's
201+
// pattern are dropped.
202+
}
203+
_ => unreachable!(),
204+
}
205+
206+
match PrintOnDrop("Dropped in the enclosing temporary scope") {
207+
// When guard evaluation fails, control-flow leaves the arm scope,
208+
// causing bindings and temporaries from earlier pattern-matching
209+
// guard condition operands to be dropped. This occurs before evaluating
210+
// the next arm's guard or body.
211+
_ if let y = PrintOnDrop("Dropped in the first arm's scope")
212+
&& false => unreachable!(),
213+
// When a guard is executed multiple times due to self-overlapping
214+
// or-patterns, control-flow leaves the arm scope when the guard fails
215+
// and re-enters the arm scope before executing the guard again.
216+
_ | _ if let y = PrintOnDrop("Dropped in the second arm's scope twice")
217+
&& false => unreachable!(),
218+
_ => {},
219+
}
220+
```
221+
197222
r[destructors.scope.bindings.patterns]
198223
Variables in patterns are dropped in reverse order of declaration within the pattern.
199224

@@ -264,9 +289,8 @@ smallest scope that contains the expression and is one of the following:
264289
* A statement.
265290
* The body of an [`if`], [`while`] or [`loop`] expression.
266291
* 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.
292+
* The non-pattern matching condition expression of an `if` or `while` expression or a non-pattern-matching `match` guard condition operand.
293+
* The pattern-matching guard, if present, and body expression for a `match` arm.
270294
* Each operand of a [lazy boolean expression].
271295
* The pattern-matching condition(s) and consequent body of [`if`] ([destructors.scope.temporary.edition2024]).
272296
* The pattern-matching condition and loop body of [`while`].
@@ -326,8 +350,16 @@ while let x = PrintOnDrop("while let scrutinee").0 {
326350
// Scrutinee is dropped at the end of the function, before local variables
327351
// (because this is the tail expression of the function body block).
328352
match PrintOnDrop("Matched value in final expression") {
329-
// Dropped once the condition has been evaluated
353+
// Non-pattern-matching guards' temporaries are dropped once the
354+
// condition has been evaluated
330355
_ if PrintOnDrop("guard condition").0 == "" => (),
356+
// Pattern-matching guards' temporaries are dropped when leaving the
357+
// arm's scope
358+
_ if let "guard scrutinee" = PrintOnDrop("guard scrutinee").0 => {
359+
let _ = &PrintOnDrop("lifetime-extended temporary in inner scope");
360+
// `lifetime-extended temporary in inner scope` is dropped here
361+
}
362+
// `guard scrutinee` is dropped here
331363
_ => (),
332364
}
333365
```
@@ -484,58 +516,6 @@ let x = (&temp()).use_temp(); // ERROR
484516
# x;
485517
```
486518

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-
539519
r[destructors.forget]
540520
## Not running destructors
541521

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)