Skip to content

Commit 43d20b0

Browse files
committed
Add caveats about mutable references in consts
As it turns out, the story that "the final value of a constant cannot contain any mutable references" has some nuance to it. Let's describe these caveats. Thanks to theemathas for noticing these and to RalfJ for filling in context.
1 parent cd8d3d5 commit 43d20b0

File tree

1 file changed

+83
-6
lines changed

1 file changed

+83
-6
lines changed

src/items/constant-items.md

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,104 @@ const BITS_N_STRINGS: BitsNStrings<'static> = BitsNStrings {
4949
```
5050

5151
r[items.const.no-mut-refs]
52-
The final value of a `const` item cannot contain any mutable references.
52+
The final value of a `const` item, after the initializer is evaluated to a value that has the declared type of the constant, cannot contain any mutable references except as follows.
5353

5454
```rust
5555
# #![allow(static_mut_refs)]
5656
static mut S: u8 = 0;
57-
const C: &u8 = unsafe { &mut S }; // OK
57+
const _: &u8 = unsafe { &mut S }; // OK.
58+
// ^^^^^^
59+
// Allowed since this is coerced to `&S`.
5860
```
5961

6062
```rust
6163
# use core::sync::atomic::AtomicU8;
6264
static S: AtomicU8 = AtomicU8::new(0);
63-
const C: &AtomicU8 = &S; // OK
65+
const _: &AtomicU8 = &S; // OK.
66+
// ^^
67+
// Allowed even though the shared reference is to an interior
68+
// mutable value.
6469
```
6570

6671
```rust,compile_fail,E0080
6772
# #![allow(static_mut_refs)]
6873
static mut S: u8 = 0;
69-
const C: &mut u8 = unsafe { &mut S }; // ERROR not allowed
74+
const _: &mut u8 = unsafe { &mut S }; // ERROR.
75+
// ^^^^^^
76+
// Not allowed as the mutable reference appears in the final value.
7077
```
7178

79+
```rust,compile_fail,E0080
80+
# #![allow(static_mut_refs)]
81+
static mut S: u8 = 0;
82+
const _: &dyn Send = &unsafe { &mut S}; // ERROR.
83+
// ^^^^^^
84+
// Not allowed as the mutable reference appears in the final value,
85+
// even though type erasure occurs.
86+
```
87+
88+
Mutable references where the referent is a value of a [zero-sized type] are allowed.
89+
90+
```rust
91+
# #![allow(static_mut_refs)]
92+
static mut S: () = ();
93+
const _: &mut () = unsafe { &mut S }; // OK.
94+
// ^^ This is a zero-sized type.
95+
```
96+
97+
```rust
98+
# #![allow(static_mut_refs)]
99+
static mut S: [u8; 0] = [0; 0];
100+
const _: &mut [u8; 0] = unsafe { &mut S }; // OK.
101+
// ^^^^^^^ This is a zero-sized type.
102+
```
103+
104+
> [!NOTE]
105+
> This is allowed as, for a value of a zero-sized type, no bytes can actually be mutated. We must accept this as `&mut []` is [promoted].
106+
107+
Values of [union type] are not considered to contain any references; for this purpose, a value of union type is treated as a sequence of untyped bytes.
108+
109+
```rust
110+
# #![allow(static_mut_refs)]
111+
union U { f: &'static mut u8 }
112+
static mut S: u8 = 0;
113+
const _: U = unsafe { U { f: &mut S }}; // OK.
114+
// ^^^^^^^^^^^^^^^
115+
// This is treated as a sequence of untyped bytes.
116+
```
117+
118+
Mutable references contained within a [mutable static] may be referenced in the final value of a constant.
119+
120+
```rust
121+
# #![allow(static_mut_refs)]
122+
static mut S: &mut u8 = unsafe { static mut I: u8 = 0; &mut I };
123+
const _: &&mut u8 = unsafe { &S }; // OK.
124+
// ^^^^^^^
125+
// This mutable reference comes from a `static mut`.
126+
```
127+
128+
> [!NOTE]
129+
> This is allowed as it's separately not allowed to read from a mutable static during constant evaluation. See [const-eval.const-expr.path-static].
130+
131+
Mutable references contained within an [external static] may be referenced in the final value of a constant.
132+
133+
```rust
134+
# #![allow(static_mut_refs)]
135+
unsafe extern "C" { static S: &'static mut u8; }
136+
const _: &&mut u8 = unsafe { &S }; // OK.
137+
// ^^^^^^^
138+
// This mutable references comes from an extern static.
139+
```
140+
141+
> [!NOTE]
142+
> This is allowed as it's separately not allowed to read from an external static during constant evaluation. See [const-eval.const-expr.path-static].
143+
72144
> [!NOTE]
73145
> We also disallow, in the final value, shared references to mutable statics created in the initializer for a separate reason. Consider:
74146
>
75147
> ```rust,compile_fail,E0492
76148
> # use core::sync::atomic::AtomicU8;
77-
> const C: &AtomicU8 = &AtomicU8::new(0); // ERROR
149+
> const _: &AtomicU8 = &AtomicU8::new(0); // ERROR.
78150
> ```
79151
>
80152
> Here, the `AtomicU8` is a temporary that is lifetime extended to `'static` (see [destructors.scope.lifetime-extension.static]), and references to lifetime-extended temporaries with interior mutability are not allowed in the final value of a constant expression (see [const-eval.const-expr.borrows]).
@@ -154,10 +226,15 @@ fn unused_generic_function<T>() {
154226
[const_eval]: ../const_eval.md
155227
[associated constant]: ../items/associated-items.md#associated-constants
156228
[constant value]: ../const_eval.md#constant-expressions
229+
[external static]: items.extern.static
157230
[free]: ../glossary.md#free-item
158231
[static lifetime elision]: ../lifetime-elision.md#const-and-static-elision
159232
[trait definition]: traits.md
160233
[underscore imports]: use-declarations.md#underscore-imports
161234
[`Copy`]: ../special-types-and-traits.md#copy
162235
[value namespace]: ../names/namespaces.md
163-
[promotion]: ../destructors.md#constant-promotion
236+
[mutable static]: items.static.mut
237+
[promoted]: destructors.scope.const-promotion
238+
[promotion]: destructors.scope.const-promotion
239+
[union type]: type.union
240+
[zero-sized type]: layout.properties.size

0 commit comments

Comments
 (0)