Skip to content

Commit af42b06

Browse files
committed
Describe pointer fragment restriction in const final values
Let's add examples and explanatory notes to clarify the restriction that the representation of the final value of a constant or static initializer must only contain bytes with provenance in whole-pointer groups. We'll add a `compile_fail` example demonstrating how storing a pointer that extends into padding creates pointer fragments in the final value, causing compilation to fail and show to work around this by explicitly zeroing the padding bytes. Let's extend the existing note about uninitialized padding bytes to provide deeper intuition about this restriction and explain how constant evaluation makes the details of typed copies observable (whether field-by-field or memory-block), how these details are not yet fully specified in Rust, and why the compiler must be allowed to reject initializers with uninitialized padding bytes to preserve future flexibility (such as always setting padding to uninitialized). Context: - rust-lang/rust#148470 - rust-lang/rust#148967
1 parent c461766 commit af42b06

1 file changed

Lines changed: 53 additions & 0 deletions

File tree

src/const_eval.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,61 @@ r[const-eval.const-expr.if-match]
237237
r[const-eval.const-expr.final-value-provenance]
238238
The representation of the final value of a [constant][constant initializer] or [static initializer] must only contain bytes with provenance in whole-pointer groups. If a byte has provenance but is not part of an adjacent group of bytes that form an entire pointer, compilation will fail.
239239

240+
```rust,compile_fail
241+
# use core::mem::MaybeUninit;
242+
#
243+
#[repr(C, align(32))]
244+
struct Pair {
245+
x: u128, // Offset 0, 16 bytes.
246+
y: MaybeUninit<u64>, // Offset 16, 8 bytes.
247+
// Offset 24, 8 bytes of padding.
248+
}
249+
250+
const _: Pair = unsafe {
251+
// ^^^^^^^ ERROR: Partial pointer in final value of constant.
252+
let mut m = MaybeUninit::<Pair>::uninit();
253+
// Store pointer that extends halfway into trailing padding.
254+
m.as_mut_ptr().byte_add(20).cast::<&u8>().write_unaligned(&0);
255+
// Initialize fields.
256+
(*m.as_mut_ptr()).x = 0;
257+
(*m.as_mut_ptr()).y = MaybeUninit::new(0);
258+
// Now `m` contains a pointer fragment in the padding.
259+
m.assume_init()
260+
};
261+
```
262+
240263
> [!NOTE]
241264
> If a byte in the representation of the final value is uninitialized, then it *may* end up having provenance, which can cause compilation to fail. `rustc` tries (but does not guarantee) to only actually fail if the initializer copies or overwrites parts of a pointer and those bytes appear in the final value.
265+
>
266+
> E.g., `rustc` currently accepts this, even though the padding bytes are uninitialized:
267+
>
268+
> ```rust
269+
> #[repr(C, align(32))]
270+
> struct Pair { x: u128, y: u64 }
271+
> // The padding bytes are uninitialized.
272+
> const _: Pair = Pair { x: 0, y: 0 }; // OK.
273+
> ```
274+
>
275+
> Constant evaluation makes the details of typed copies observable: depending on whether a copy is performed field-by-field or as a memory-block copy, provenance in padding bytes might be discarded or preserved (both in the source and in the destination). The language allows the compiler to reject any final initializer value with an uninitialized padding byte to preserve implementation flexibility (e.g., the compiler may in the future always set padding bytes to uninitialized).
276+
>
277+
> Manually initializing (e.g., zeroing) the padding bytes ensures the final value is accepted:
278+
>
279+
> ```rust
280+
> # use std::mem::MaybeUninit;
281+
> # #[repr(C, align(32))]
282+
> # struct Pair { x: u128, y: u64 }
283+
> const _: Pair = unsafe { // OK.
284+
> let mut m = Pair { x: 0, y: 0 };
285+
> # // Trigger failure if padding were not zeroed.
286+
> # let ptr: *const u8 = &0;
287+
> # let ptr_bytes = &raw const ptr as *const MaybeUninit<u8>;
288+
> # let dst: *mut MaybeUninit<u8> = (&raw mut m).byte_add(31).cast();
289+
> # dst.write(ptr_bytes.read());
290+
> // Explicitly zero the padding.
291+
> (&raw mut m).byte_add(24).cast::<u64>().write_unaligned(0);
292+
> m
293+
> };
294+
> ```
242295
243296
r[const-eval.const-context]
244297
## Const context

0 commit comments

Comments
 (0)