Skip to content

Commit 53472f3

Browse files
edition guide for temporary lifetime changes
1 parent cb41a09 commit 53472f3

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Lifetime adjustments to temporary values
2+
3+
🚧 The 2024 Edition has not yet been released and hence this section is still "under construction".
4+
More information may be found in the tracking issue at <https://github.com/rust-lang/rust/issues/124085> and <https://github.com/rust-lang/rust/issues/123739>.
5+
6+
## Summary
7+
8+
This Edition observes two breaking changes to the handling of lifetimes assigned to temporary values, in the following two cases.
9+
10+
- In `if let $pat = $expr { .. } else { .. }` expression, the temporary values generated from evaluating `$expr` will be dropped before the program enters the `else` branch.
11+
- Temporary values generated in evaluation of the [tail expression] of a [function] or closure body, or a [block] are instead dropped before local variables are dropped with Edition 2024.
12+
13+
[function]: ../../reference/items/functions.html
14+
[block]: ../../reference/expressions/block-expr.html
15+
[tail expression]: ../../reference/items/expressions.html
16+
17+
## Details
18+
19+
Edition 2024 brings about improvements in handling lifetime of temporary values, to reduce expected retention of such values and surprising runtime behaviour thereof. First it involves an `if let` expression. Up until Edition 2021, `if let` as an expression assigns a lifetime that can extend beyond the `if let` expression itself.
20+
21+
```rust,edition2021
22+
// Up until Edition 2021
23+
call(
24+
another_call(if let Enum::Pattern { value } = &self.get_value().method() {
25+
Some(value)
26+
} else {
27+
None
28+
})
29+
);
30+
```
31+
32+
In this example, the temporary value generated from evaluating the sub-expression `self.get_value()` will only be dropped at the end of the semicolon, even though at that program point its last use as a function argument for `another_call` is exhausted. Edition 2024 makes a correction to this lifetime assignment by shortening it up until the point where the then-block is completely evaluated or the program control enters the `else` block.
33+
34+
```rust,edition2024
35+
// From Edition 2024
36+
call(
37+
another_call(if let Enum::Pattern { value } = &self.get_value().method() {
38+
Some(value)
39+
}
40+
// At this point, `self.get_value()` is dropped
41+
else {
42+
None
43+
})
44+
);
45+
```
46+
47+
The other lifetime assignment rule changed by Edition 2024 involves tail expressions. It often comes as a surprise that, up until Edition 2021, temporary values in tail expressions are dropped later than the local variable bindings, as in the following example.
48+
49+
```rust,edition2021
50+
// Up until Edition 2021
51+
fn main() {
52+
let m = std::sync::Mutex::new(());
53+
*m.lock().unwrap() // error[E0597]: `m` does not live long enough
54+
}
55+
```
56+
57+
This yields the following error with Edition 2021.
58+
59+
```
60+
error[E0597]: `m` does not live long enough
61+
--> src/main.rs:3:6
62+
|
63+
2 | let m = std::sync::Mutex::new(());
64+
| - binding `m` declared here
65+
3 | *m.lock().unwrap()
66+
| ^----------------
67+
| |
68+
| borrowed value does not live long enough
69+
| a temporary with access to the borrow is created here ...
70+
4 | //
71+
5 | }
72+
| -
73+
| |
74+
| `m` dropped here while still borrowed
75+
| ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `MutexGuard`
76+
|
77+
help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
78+
|
79+
3 | *m.lock().unwrap();
80+
| +
81+
```
82+
83+
Edition 2024 intends to make correction, so that the temporary value `m.lock().unwrap` is dropped first, followed by dropping the local variable `m`.
84+
85+
## Migration
86+
87+
It is always safe to rewrite `if let` with a `match`. Edition 2024 comes with an non-enforcing lint `if_let_rescope` which suggests a fix when a lifetime issue arises due to this change, or the lint detects that a temporary value with a custom, non-trivial `Drop` destructor is generated from the right-hand side of the `if let`. For instance, the earlier example involving `call` and `another_call` may be rewritten into the following, when the suggestion from `cargo fix` is accepted.
88+
89+
```rust,edition2024
90+
call(
91+
another_call(match &self.get_value().method() {
92+
Enum::Pattern { value } => {
93+
Some(value)
94+
}
95+
_ => {
96+
None
97+
}
98+
})
99+
);
100+
// `self.get_value()` is dropped at this point, which exactly matches Edition 2021 semantics
101+
```
102+
103+
Edition 2024 cannot deduce with complete confidence that the program semantics are preserved when the lifetime of such temporary values are shortened. For this reason, this lint remains non-binding but users are encouraged to set its level to `warn` in order to allow manual auditing.
104+
105+
Unfortunately, there is no semantics-preserving rewrite to shortened lifetime for temporary values in tail expressions, with the implementation of [^RFC3606]. A lint `tail_expr_drop_order` is introduced to detect if a temporary value with a custom, non-trivial `Drop` destructor is generated in a tail expression. Users are again encouraged to set its level to `warn` so that the temporary values could be audited with respect to the change.
106+
107+
[^RFC3606]: Details are documented at [RFC 3606](https://github.com/rust-lang/rfcs/pull/3606)

0 commit comments

Comments
 (0)