|
| 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