Skip to content

Commit 5f11676

Browse files
authored
Merge pull request #318 from dingxiangfei2009/edition-2024-temporary-lifetime
Edition 2024 guide for temporary lifetime changes
2 parents 07766cb + bf89d94 commit 5f11676

File tree

3 files changed

+179
-0
lines changed

3 files changed

+179
-0
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,5 @@
6161
- [Rustdoc combined tests](rust-2024/rustdoc-doctests.md)
6262
- [Rustdoc nested `include!` change](rust-2024/rustdoc-nested-includes.md)
6363
- [Reserved syntax](rust-2024/reserved-syntax.md)
64+
- [`if let` temporary scope](rust-2024/temporary-if-let-scope.md)
65+
- [Tail expression temporary scope](rust-2024/temporary-tail-expr-scope.md)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# `if let` temporary scope
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>.
5+
6+
## Summary
7+
8+
- In an `if let $pat = $expr { .. } else { .. }` expression, the temporary values generated from evaluating `$expr` will be dropped before the program enters the `else` branch instead of after.
9+
10+
## Details
11+
12+
The 2024 Edition changes the drop scope of [temporary values] in the scrutinee[^scrutinee] of an `if let` expression. This is intended to help reduce the potentially unexpected behavior involved with the temporary living for too long.
13+
14+
Before 2024, the temporaries could be extended beyond the `if let` expression itself. For example:
15+
16+
```rust,edition2021
17+
// Before 2024
18+
# use std::sync::RwLock;
19+
20+
fn f(value: &RwLock<Option<bool>>) {
21+
if let Some(x) = *value.read().unwrap() {
22+
println!("value is {x}");
23+
} else {
24+
let mut v = value.write().unwrap();
25+
if v.is_none() {
26+
*v = Some(true);
27+
}
28+
}
29+
// <--- Read lock is dropped here in 2021
30+
}
31+
```
32+
33+
In this example, the temporary read lock generated by the call to `value.read()` will not be dropped until after the `if let` expression (that is, after the `else` block). In the case where the `else` block is executed, this causes a deadlock when it attempts to acquire a write lock.
34+
35+
The 2024 Edition shortens the lifetime of the temporaries to the point where the then-block is completely evaluated or the program control enters the `else` block.
36+
37+
<!-- TODO: edition2024 -->
38+
```rust
39+
// Starting with 2024
40+
# use std::sync::RwLock;
41+
42+
fn f(value: &RwLock<Option<bool>>) {
43+
if let Some(x) = *value.read().unwrap() {
44+
println!("value is {x}");
45+
}
46+
// <--- Read lock is dropped here in 2024
47+
else {
48+
let mut s = value.write().unwrap();
49+
if s.is_none() {
50+
*s = Some(true);
51+
}
52+
}
53+
}
54+
```
55+
56+
See the [temporary scope rules] for more information about how temporary scopes are extended. See the [tail expression temporary scope] chapter for a similar change made to tail expressions.
57+
58+
[^scrutinee]: The [scrutinee] is the expression being matched on in the `if let` expression.
59+
60+
[scrutinee]: ../../reference/glossary.html#scrutinee
61+
[temporary values]: ../../reference/expressions.html#temporaries
62+
[temporary scope rules]: ../../reference/destructors.html#temporary-scopes
63+
[tail expression temporary scope]: temporary-tail-expr-scope.md
64+
65+
## Migration
66+
67+
It is always safe to rewrite `if let` with a `match`. The temporaries of the `match` scrutinee are extended past the end of the `match` expression (typically to the end of the statement), which is the same as the 2021 behavior of `if let`.
68+
69+
The [`if_let_rescope`] lint 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 scrutinee of the `if let`. For instance, the earlier example may be rewritten into the following when the suggestion from `cargo fix` is accepted:
70+
71+
```rust
72+
# use std::sync::RwLock;
73+
fn f(value: &RwLock<Option<bool>>) {
74+
match *value.read().unwrap() {
75+
Some(x) => {
76+
println!("value is {x}");
77+
}
78+
_ => {
79+
let mut s = value.write().unwrap();
80+
if s.is_none() {
81+
*s = Some(true);
82+
}
83+
}
84+
}
85+
// <--- Read lock is dropped here in both 2021 and 2024
86+
}
87+
```
88+
89+
In this particular example, that's probably not what you want due to the aforementioned deadlock! However, some scenarios may be assuming that the temporaries are held past the `else` clause, in which case you may want to retain the old behavior.
90+
91+
The `if_let_rescope` lint cannot deduce with complete confidence that the program semantics are preserved when the lifetime of such temporary values are shortened. For this reason, the suggestion from this lint is *not* automatically applied when running `cargo fix --edition`. It is recommended to manually inspect the warnings emitted when running `cargo fix --edition` and determine whether or not you need to apply the suggestion.
92+
93+
If you want to manually inspect these warnings without performing the edition migration, you can enable the lint with:
94+
95+
```rust
96+
// Add this to the root of your crate to do a manual migration.
97+
#![warn(if_let_rescope)]
98+
```
99+
100+
[`if_let_rescope`]: ../../rustc/lints/listing/allowed-by-default.html#if-let-rescope
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Tail expression temporary scope
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/123739>.
5+
6+
## Summary
7+
8+
- Temporary values generated in evaluation of the tail expression of a [function] or closure body, or a [block] are now dropped before local variables.
9+
10+
[function]: ../../reference/items/functions.html
11+
[block]: ../../reference/expressions/block-expr.html
12+
13+
## Details
14+
15+
The 2024 Edition changes the drop order of [temporary values] in tail expressions. It often comes as a surprise that, before the 2024 Edition, temporary values in tail expressions are dropped later than the local variable bindings, as in the following example:
16+
17+
[temporary values]: ../../reference/expressions.html#temporaries
18+
19+
```rust,edition2021,compile_fail,E0597
20+
// Before 2024
21+
# use std::cell::RefCell;
22+
fn f() -> usize {
23+
let c = RefCell::new("..");
24+
c.borrow().len() // error[E0597]: `c` does not live long enough
25+
}
26+
```
27+
28+
This yields the following error with the 2021 Edition:
29+
30+
```text
31+
error[E0597]: `c` does not live long enough
32+
--> src/lib.rs:4:5
33+
|
34+
3 | let c = RefCell::new("..");
35+
| - binding `c` declared here
36+
4 | c.borrow().len() // error[E0597]: `c` does not live long enough
37+
| ^---------
38+
| |
39+
| borrowed value does not live long enough
40+
| a temporary with access to the borrow is created here ...
41+
5 | }
42+
| -
43+
| |
44+
| `c` dropped here while still borrowed
45+
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
46+
|
47+
= note: the temporary is part of an expression at the end of a block;
48+
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
49+
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
50+
|
51+
4 | let x = c.borrow().len(); x // error[E0597]: `c` does not live long enough
52+
| +++++++ +++
53+
54+
For more information about this error, try `rustc --explain E0597`.
55+
```
56+
57+
In 2021 the local variable `c` is dropped before the temporary created by `c.borrow()`. The 2024 Edition changes this so that the temporary value `c.borrow()` is dropped first, followed by dropping the local variable `c`, allowing the code to compile as expected.
58+
59+
See the [temporary scope rules] for more information about how temporary scopes are extended. See the [`if let` temporary scope] chapter for a similar change made to `if let` expressions.
60+
61+
[`if let` temporary scope]: temporary-if-let-scope.md
62+
[temporary scope rules]: ../../reference/destructors.html#temporary-scopes
63+
64+
## Migration
65+
66+
Unfortunately, there are no semantics-preserving rewrites to shorten the lifetime for temporary values in tail expressions[^RFC3606]. The [`tail_expr_drop_order`] lint detects if a temporary value with a custom, non-trivial `Drop` destructor is generated in a tail expression. Warnings from this lint will appear when running `cargo fix --edition`, but will otherwise not automatically make any changes. It is recommended to manually inspect the warnings and determine whether or not you need to make any adjustments.
67+
68+
If you want to manually inspect these warnings without performing the edition migration, you can enable the lint with:
69+
70+
```rust
71+
// Add this to the root of your crate to do a manual migration.
72+
#![warn(tail_expr_drop_order)]
73+
```
74+
75+
[^RFC3606]: Details are documented at [RFC 3606](https://github.com/rust-lang/rfcs/pull/3606)
76+
77+
[`tail_expr_drop_order`]: ../../rustc/lints/listing/allowed-by-default.html#tail-expr-drop-order

0 commit comments

Comments
 (0)