From 48587c779f53b0c1f3cdd16b047b76de43c7c0ee Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 26 Aug 2025 09:14:45 -0700 Subject: [PATCH 1/5] specify lifetime extension of `match` arms and `if` tail expressions --- src/destructors.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/destructors.md b/src/destructors.md index 24ed38e2f..6d6226d35 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -435,6 +435,8 @@ expression which is one of the following: expression. * The arguments to an extending [tuple struct] or [tuple variant] constructor expression. * The final expression of any extending [block expression]. +* The arm(s) of an extending [`match`] expression. +* The final expressions of an extending [`if`] expression's consequent, `else if`, and `else` blocks. So the borrow expressions in `&mut 0`, `(&1, &mut 2)`, and `Some(&mut 3)` are all extending expressions. The borrows in `&0 + &1` and `f(&mut 0)` are not. @@ -458,6 +460,10 @@ let x = (&*&temp(),); # x; let x = { [Some(&temp()) ] }; # x; +let x = match () { _ => &temp() }; +# x; +let x = if true { &temp() } else { &temp() }; +# x; let ref x = temp(); # x; let ref x = *&temp(); @@ -477,6 +483,8 @@ let x = std::convert::identity(&temp()); // ERROR # x; let x = (&temp()).use_temp(); // ERROR # x; +let x = match &temp() { x => x }; // ERROR +# x; ``` r[destructors.forget] From c367664f20a2e1a720a42aa76b20be311a6babe0 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Thu, 4 Sep 2025 05:46:27 +0000 Subject: [PATCH 2/5] Revise and extend text on extending expressions In particular, let's note that the final expression of an async block expression is not extending; we need to say that as we otherwise say that the final expression of a block expression is extending, and async blocks are ostensibly blocks in our ontology. Let's build the pass tests with Rust 2024 to ensure that we're not relying on the old edition tail expression rule. When talking about an arm of an extending match expression, let's be clear that we're talking about the arm expression. Let's break apart all of the failure tests; otherwise we can't be sure that they're all failing, and let's mark the expected error code for each failure. For each test, let's note the applicable rule for the outermost part of the expression. Let's add some tests to show that the operand of `break` is not extending, as that might be a bit surprising. Let's demonstrate explicitly that raw borrows count as borrows. --- src/destructors.md | 91 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/src/destructors.md b/src/destructors.md index 6d6226d35..ff62b0011 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -434,9 +434,9 @@ expression which is one of the following: expression], [braced struct][struct expression], or [tuple][tuple expression] expression. * The arguments to an extending [tuple struct] or [tuple variant] constructor expression. -* The final expression of any extending [block expression]. -* The arm(s) of an extending [`match`] expression. -* The final expressions of an extending [`if`] expression's consequent, `else if`, and `else` blocks. +* The final expression of an extending [block expression] except for an [async block expression]. +* The final expression of an extending [`if`] expression's consequent, `else if`, or `else` block. +* An arm expression of an extending [`match`] expression. So the borrow expressions in `&mut 0`, `(&1, &mut 2)`, and `Some(&mut 3)` are all extending expressions. The borrows in `&0 + &1` and `f(&mut 0)` are not. @@ -448,42 +448,92 @@ extended. Here are some examples where expressions have extended temporary scopes: -```rust -# fn temp() {} -// The temporary that stores the result of `temp()` lives in the same scope -// as x in these cases. -let x = &temp(); +```rust,edition2024 +# use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; +# static X: AtomicU64 = AtomicU64::new(0); +# struct S; +# impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } } +# const fn temp() -> S { S } +let x = &temp(); // Operand of borrow. # x; -let x = &temp() as &dyn Send; +let x = &raw const *&temp(); // Operand of raw borrow. +# assert_eq!(X.load(Relaxed), 0); +let x = &temp() as &dyn Send; // Operand of cast. # x; -let x = (&*&temp(),); +let x = (&*&temp(),); // Operand of tuple constructor. # x; -let x = { [Some(&temp()) ] }; +let x = { [Some(&temp())] }; // Final expr of block. # x; -let x = match () { _ => &temp() }; +let x = const { &temp() }; // Final expr of `const` block. +# x; +let x = unsafe { &temp() }; // Final expr of `unsafe` block. # x; let x = if true { &temp() } else { &temp() }; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// Final exprs of `if`/`else` blocks. +# x; +let x = match () { _ => &temp() }; // `match` arm expression. # x; -let ref x = temp(); +let ref x = temp(); // Initializer expression. # x; -let ref x = *&temp(); +let ref x = *&temp(); // Initializer expression. # x; +// +// All of the temporaries above are still live here. +# assert_eq!(X.load(Relaxed), 0); ``` Here are some examples where expressions don't have extended temporary scopes: -```rust,compile_fail +```rust,compile_fail,E0716 +# fn temp() {} +// Arguments to function calls are not extending expressions. The +// temporary is dropped at the semicolon. +let x = core::convert::identity(&temp()); // ERROR +# x; +``` + +```rust,compile_fail,E0716 # fn temp() {} # trait Use { fn use_temp(&self) -> &Self { self } } # impl Use for () {} -// The temporary that stores the result of `temp()` only lives until the -// end of the let statement in these cases. +// Receivers of method calls are not extending expressions. +let x = (&temp()).use_temp(); // ERROR +# x; +``` + +```rust,compile_fail,E0716 +# fn temp() {} +// Scrutinees of match expressions are not extending expressions. +let x = match &temp() { x => x }; // ERROR +# x; +``` -let x = std::convert::identity(&temp()); // ERROR +```rust,compile_fail,E0515 +# fn temp() {} +// Final expressions of `async` blocks are not extending expressions. +let x = async { &temp() }; // ERROR # x; -let x = (&temp()).use_temp(); // ERROR +``` + +```rust,compile_fail,E0515 +# fn temp() {} +// Final expressions of closures are not extending expressions. +let x = || &temp(); // ERROR # x; -let x = match &temp() { x => x }; // ERROR +``` + +```rust,compile_fail,E0716 +# fn temp() {} +// Operands of loop breaks are not extending expressions. +let x = loop { break &temp() }; // ERROR +# x; +``` + +```rust,compile_fail,E0716 +# fn temp() {} +// Operands of breaks to labels are not extending expressions. +let x = 'a: { break 'a &temp() }; // ERROR # x; ``` @@ -544,6 +594,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [tuple variant]: type.enum.declaration [array expression]: expressions/array-expr.md#array-expressions +[async block expression]: expr.block.async [block expression]: expressions/block-expr.md [borrow expression]: expressions/operator-expr.md#borrow-operators [cast expression]: expressions/operator-expr.md#type-cast-expressions From 7712fe419dbf8bb4ef68dbd401b5b2e4893b8139 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Thu, 4 Sep 2025 06:47:10 +0000 Subject: [PATCH 3/5] Add note about non-extension of array repeat operands We document that the operands of an extending array expression are extending expressions, but `rustc` doesn't treat the repeat operand that way. Let's add a note about that flagging it as an open question and link to the relevant issue. --- src/destructors.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/destructors.md b/src/destructors.md index ff62b0011..3a08fb236 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -444,6 +444,11 @@ are all extending expressions. The borrows in `&0 + &1` and `f(&mut 0)` are not. The operand of any extending borrow expression has its temporary scope extended. +> [!NOTE] +> `rustc` does not treat [array repeat operands] of extending [array] expressions as extending expressions. Whether it should is an open question. +> +> For details, see [Rust issue #146092](https://github.com/rust-lang/rust/issues/146092). + #### Examples Here are some examples where expressions have extended temporary scopes: @@ -594,6 +599,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [tuple variant]: type.enum.declaration [array expression]: expressions/array-expr.md#array-expressions +[array repeat operands]: expr.array.repeat-operand [async block expression]: expr.block.async [block expression]: expressions/block-expr.md [borrow expression]: expressions/operator-expr.md#borrow-operators From f45f40d1484f9d5988d34ba5b0b5e5ea07339850 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Thu, 4 Sep 2025 06:59:13 +0000 Subject: [PATCH 4/5] Generalize item about edition test annotations In our authoring guide, we describe how to control the edition for a test. This was written as though the only two editions were 2015 and 2018. Let's update the wording to make this more general. --- docs/authoring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authoring.md b/docs/authoring.md index 1c158486a..996a0b1ee 100644 --- a/docs/authoring.md +++ b/docs/authoring.md @@ -47,7 +47,7 @@ See Date: Thu, 4 Sep 2025 07:00:40 +0000 Subject: [PATCH 5/5] Add note about breaking up failure cases in tests When writing tests for failure cases, we need to break up each case into a separate code block so that each is tested properly. Let's describe this in our authoring guidelines. --- docs/authoring.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/authoring.md b/docs/authoring.md index 996a0b1ee..e51118308 100644 --- a/docs/authoring.md +++ b/docs/authoring.md @@ -54,6 +54,8 @@ Rust examples are tested via rustdoc, and should include the appropriate annotat * `ignore` --- The example shouldn't be built or tested. This should be avoided if possible. Usually this is only necessary when the testing framework does not support it (such as external crates or modules, or a proc-macro), or it contains pseudo-code which is not valid Rust. An HTML comment such as `` should be placed before the example to explain why it is ignored. * `Exxxx` --- If the example is expected to fail to compile with a specific error code, include that code so that rustdoc will check that the expected code is used. +When demonstrating success cases, many such cases may be included in a single code block. For failure cases, however, each example must appear in a separate code block so that the tests can ensure that each case indeed fails and fails with the appropriate error code or codes. + See the [rustdoc documentation] for more detail. [rustdoc documentation]: https://doc.rust-lang.org/rustdoc/documentation-tests.html