Skip to content

Commit 6091e79

Browse files
committed
Revise, clarify, and add examples
Rather than talking about lifetime-extended temporaries in the top-level scope of an initializer, which is maybe a bit ambiguous, let's speak directly to the result of the lifetime extension, which is that these temporaries disallowed for borrows would have their lifetimes extended to the end of the program. Let's also speak about place expressions, rather than places, as that's more precise here. We'll add examples throughout. Thanks to RalfJ for the substance of many of these.
1 parent 62a33ad commit 6091e79

File tree

1 file changed

+124
-9
lines changed

1 file changed

+124
-9
lines changed

src/const_eval.md

Lines changed: 124 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,127 @@ r[const-eval.const-expr.builtin-arith-logic]
8181
operators used on integer and floating point types, `bool`, and `char`.
8282

8383
r[const-eval.const-expr.borrows]
84-
* All forms of [borrow]s, including raw borrows, with one limitation:
85-
mutable borrows and shared borrows to values with interior mutability
86-
are not allowed to refer to [lifetime-extended temporaries in the top-level scope of a `const` or `static` initializer expression][lifetime-extension-const].
87-
88-
In other words, they are only allowed to refer to *transient* places, to *indirect* places, or to *static* places.
89-
A place is *transient* if it is based on a local variable whose lifetime is strictly contained inside the current [const context].
90-
A place is *indirect* if it is based on a [dereference expression][dereference operator].
91-
A place is *static* if it is based on a `static` item or a [promoted expression].
84+
* All forms of [borrow]s, including raw borrows, except:
85+
86+
* Mutable borrows of expressions whose temporary scopes would be extended (see [temporary lifetime extension]) to the end of the program.
87+
* Shared borrows of expressions whose temporary scopes would be extended to the end of the program when those expressions result in values with interior mutability.
88+
89+
```rust,compile_fail,E0764
90+
// Due to being in tail position, this borrow extends the scope of the
91+
// temporary to the end of the program. Since the borrow is mutable,
92+
// this is not allowed in a const expression.
93+
const C: &[u8] = &mut []; // ERROR not allowed
94+
```
95+
96+
```rust,compile_fail,E0764
97+
// Const blocks are similar to initializers of `const` items.
98+
let _: &[u8] = const { &mut [] }; // ERROR not allowed
99+
```
100+
101+
```rust,compile_fail,E0492
102+
# use core::sync::atomic::AtomicU8;
103+
// This is not allowed as 1) the temporary scope is extended to the
104+
// end of the program and 2) the temporary has interior mutability.
105+
const C: &AtomicU8 = &AtomicU8::new(0); // ERROR not allowed
106+
```
107+
108+
```rust,compile_fail,E0492
109+
# use core::sync::atomic::AtomicU8;
110+
// As above.
111+
let _: &_ = const { &AtomicU8::new(0) }; // ERROR not allowed
112+
```
113+
114+
```rust
115+
// Even though the borrow is mutable and the temporary lives to the
116+
// end of the program due to promotion, this is allowed because the
117+
// borrow is not in tail position and so the scope of the temporary
118+
// is not extended via temporary lifetime extension.
119+
const C: () = { let _: &'static mut [u8] = &mut []; }; // OK
120+
// ~~
121+
// Promoted temporary.
122+
```
123+
124+
```rust
125+
# use core::sync::atomic::AtomicU8;
126+
// This shared borrow of an interior mutable temporary is allowed
127+
// because its scope is not extended.
128+
const C: () = { _ = &AtomicU8::new(0); }; // OK
129+
```
130+
131+
```rust
132+
# #![allow(static_mut_refs)]
133+
// Even though this borrow is mutable, it's not of a temporary, so
134+
// this is allowed.
135+
const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; // OK
136+
```
137+
138+
```rust
139+
# use core::sync::atomic::AtomicU8;
140+
// Even though this borrow is of a value with interior mutability,
141+
// it's not of a temporary, so this is allowed.
142+
const C: &AtomicU8 = {
143+
static S: AtomicU8 = AtomicU8::new(0); &S // OK
144+
};
145+
```
146+
147+
> [!NOTE]
148+
> In other words, shared borrows of interior mutable data and mutable borrows are only allowed in a const context when the borrowed place expression is *transient*, *indirect*, or *static*.
149+
>
150+
> A place expression is *transient* if it is a variable local to the current [const context] or an expression whose temporary scope is contained inside the current const context.
151+
>
152+
> ```rust
153+
> // The borrow is of a variable local to the initializer, therefore
154+
> // this place expresssion is transient.
155+
> const C: () = { let mut x = 0; _ = &mut x; };
156+
> ```
157+
>
158+
> ```rust
159+
> // The borrow is of a temporary whose scope has not been extended,
160+
> // therefore this place expression is transient.
161+
> const C: () = { _ = &mut 0u8; };
162+
> ```
163+
>
164+
> ```rust
165+
> // When a temporary is promoted but not lifetime extended, its
166+
> // place expression is still treated as transient.
167+
> const C: () = { let _: &'static mut [u8] = &mut []; };
168+
> ```
169+
>
170+
> A place expression is *indirect* if it is a [dereference expression].
171+
>
172+
> ```rust
173+
> const C: () = { _ = &mut *(&mut 0); };
174+
> ```
175+
>
176+
> A place expression is *static* if it is a `static` item.
177+
>
178+
> ```rust
179+
> # #![allow(static_mut_refs)]
180+
> const C: &u8 = unsafe { static mut S: u8 = 0; &mut S };
181+
> ```
182+
183+
> [!NOTE]
184+
> One surprising consequence of these rules is that we allow this,
185+
>
186+
> ```rust
187+
> const C: &[u8] = { let x: &mut [u8] = &mut []; x }; // OK
188+
> // ~~~~~~~
189+
> // Empty arrays are promoted even behind mutable borrows.
190+
> ```
191+
>
192+
> but we disallow this similar code:
193+
>
194+
> ```rust,compile_fail,E0764
195+
> const C: &[u8] = &mut []; // ERROR
196+
> // ~~~~~~~
197+
> // Tail expression.
198+
> ```
199+
>
200+
> The difference between these is that, in the first, the empty array is [promoted] but its scope does not undergo [temporary lifetime extension], so we consider the place expression to be transient (even though after promotion the place indeed lives to the end of the program). In the second, the scope of the empty array temporary does undergo lifetime extension, and so it is rejected due to being a mutable borrow of a lifetime-extended temporary (and therefore borrowing a non-transient place expression).
201+
>
202+
> The effect is surprising because temporary lifetime extension, in this case, causes less code to compile than would without it.
203+
>
204+
> See [issue #143129](https://github.com/rust-lang/rust/issues/143129) for more details.
92205
93206
r[const-eval.const-expr.deref]
94207
* The [dereference operator] except for raw pointers.
@@ -178,6 +291,7 @@ of whether you are building on a `64` bit or a `32` bit system.
178291
[const generic parameters]: items/generics.md#const-generics
179292
[constants]: items/constant-items.md
180293
[Const parameters]: items/generics.md
294+
[dereference expression]: expressions/operator-expr.md#the-dereference-operator
181295
[dereference operator]: expressions/operator-expr.md#the-dereference-operator
182296
[destructors]: destructors.md
183297
[enum discriminants]: items/enumerations.md#discriminants
@@ -190,7 +304,6 @@ of whether you are building on a `64` bit or a `32` bit system.
190304
[interior mutability]: interior-mutability.md
191305
[if]: expressions/if-expr.md#if-expressions
192306
[lazy boolean]: expressions/operator-expr.md#lazy-boolean-operators
193-
[lifetime-extension-const]: destructors.md#r-destructors.scope.lifetime-extension.static
194307
[let statements]: statements.md#let-statements
195308
[literals]: expressions/literal-expr.md
196309
[logical]: expressions/operator-expr.md#arithmetic-and-logical-binary-operators
@@ -201,9 +314,11 @@ of whether you are building on a `64` bit or a `32` bit system.
201314
[paths]: expressions/path-expr.md
202315
[patterns]: patterns.md
203316
[promoted expression]: destructors.md#constant-promotion
317+
[promoted]: destructors.md#constant-promotion
204318
[range expressions]: expressions/range-expr.md
205319
[slice]: types/slice.md
206320
[statics]: items/static-items.md
207321
[struct]: expressions/struct-expr.md
322+
[temporary lifetime extension]: destructors.scope.lifetime-extension
208323
[tuple expressions]: expressions/tuple-expr.md
209324
[while]: expressions/loop-expr.md#predicate-loops

0 commit comments

Comments
 (0)