From 2bb92c286fc64459bd62d5dbc788873fbb3a2b87 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Jan 2026 11:02:16 +0100 Subject: [PATCH 1/6] const-eval: explain the final-value-byte-provenance restriction --- src/const_eval.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/const_eval.md b/src/const_eval.md index 779b00354e..295be5c1ae 100644 --- a/src/const_eval.md +++ b/src/const_eval.md @@ -232,6 +232,12 @@ r[const-eval.const-expr.loop] r[const-eval.const-expr.if-match] * [if] and [match] expressions. +r[const-eval.const-expr.final-value-provenance] +The representation of the final value of a constant or static initializer must only contain 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. + +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. +As a quality-of-implementation concern, the compiler should only actually fail if the initializer copies or overwrites parts of a pointer and that memory ends up in the final value. + r[const-eval.const-context] ## Const context [const context]: #const-context From c4617660804baed424b574e4f68ec5c4965387b2 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 20 Jan 2026 21:15:34 +0000 Subject: [PATCH 2/6] Revise text on initializer provenance restriction The text here is describing a restriction on the final value of a constant or static initializer. We could put this in the chapters for constant and static items, but then we'd have to duplicate it. At the same time, it doesn't make sense to be in the section for constant expressions since this is not a restriction on those expressions. Let's solve this, for now, by keeping it in the chapter on constant evaluation but putting it in a new "constant initializers" section. We'll move the second paragraph, which states what a compiler should do as a "quality-of-implementation" matter, into an admonition, and we'll reword this a bit to talk about what `rustc` does (but does not guarantee) to match the style we use for this in other places. We also add links to the appropriate chapters and sections for constant and static initializers. --- src/const_eval.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/const_eval.md b/src/const_eval.md index 295be5c1ae..1aaec8c891 100644 --- a/src/const_eval.md +++ b/src/const_eval.md @@ -232,11 +232,13 @@ r[const-eval.const-expr.loop] r[const-eval.const-expr.if-match] * [if] and [match] expressions. +## Const initializers + r[const-eval.const-expr.final-value-provenance] -The representation of the final value of a constant or static initializer must only contain 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. +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. -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. -As a quality-of-implementation concern, the compiler should only actually fail if the initializer copies or overwrites parts of a pointer and that memory ends up in the final value. +> [!NOTE] +> 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. r[const-eval.const-context] ## Const context @@ -313,6 +315,7 @@ The types of a const function's parameters and return type are restricted to tho [const generic argument]: items/generics.md#const-generics [const generic parameters]: items/generics.md#const-generics [constant expressions]: #constant-expressions +[constant initializer]: items.const [constants]: items/constant-items.md [Const parameters]: items/generics.md [dereference expression]: expr.deref @@ -342,6 +345,7 @@ The types of a const function's parameters and return type are restricted to tho [promoted]: destructors.md#constant-promotion [range expressions]: expressions/range-expr.md [slice]: types/slice.md +[static initializer]: items.static.init [statics]: items/static-items.md [Struct expressions]: expressions/struct-expr.md [temporary lifetime extension]: destructors.scope.lifetime-extension From af42b06685006dd5f87b2d8c6efb2aa28ed18e4f Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 20 Jan 2026 21:59:28 +0000 Subject: [PATCH 3/6] 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: - https://github.com/rust-lang/rust/issues/148470 - https://github.com/rust-lang/rust/pull/148967 --- src/const_eval.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/const_eval.md b/src/const_eval.md index 1aaec8c891..edbddbb9fe 100644 --- a/src/const_eval.md +++ b/src/const_eval.md @@ -237,8 +237,61 @@ r[const-eval.const-expr.if-match] r[const-eval.const-expr.final-value-provenance] 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. +```rust,compile_fail +# use core::mem::MaybeUninit; +# +#[repr(C, align(32))] +struct Pair { + x: u128, // Offset 0, 16 bytes. + y: MaybeUninit, // Offset 16, 8 bytes. + // Offset 24, 8 bytes of padding. +} + +const _: Pair = unsafe { +// ^^^^^^^ ERROR: Partial pointer in final value of constant. + let mut m = MaybeUninit::::uninit(); + // Store pointer that extends halfway into trailing padding. + m.as_mut_ptr().byte_add(20).cast::<&u8>().write_unaligned(&0); + // Initialize fields. + (*m.as_mut_ptr()).x = 0; + (*m.as_mut_ptr()).y = MaybeUninit::new(0); + // Now `m` contains a pointer fragment in the padding. + m.assume_init() +}; +``` + > [!NOTE] > 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. +> +> E.g., `rustc` currently accepts this, even though the padding bytes are uninitialized: +> +> ```rust +> #[repr(C, align(32))] +> struct Pair { x: u128, y: u64 } +> // The padding bytes are uninitialized. +> const _: Pair = Pair { x: 0, y: 0 }; // OK. +> ``` +> +> 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). +> +> Manually initializing (e.g., zeroing) the padding bytes ensures the final value is accepted: +> +> ```rust +> # use std::mem::MaybeUninit; +> # #[repr(C, align(32))] +> # struct Pair { x: u128, y: u64 } +> const _: Pair = unsafe { // OK. +> let mut m = Pair { x: 0, y: 0 }; +> # // Trigger failure if padding were not zeroed. +> # let ptr: *const u8 = &0; +> # let ptr_bytes = &raw const ptr as *const MaybeUninit; +> # let dst: *mut MaybeUninit = (&raw mut m).byte_add(31).cast(); +> # dst.write(ptr_bytes.read()); +> // Explicitly zero the padding. +> (&raw mut m).byte_add(24).cast::().write_unaligned(0); +> m +> }; +> ``` r[const-eval.const-context] ## Const context From 716afec2167e3d8728066f2004e4e7aa9f8c5c0c Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 22:48:32 +0000 Subject: [PATCH 4/6] Add note explaining whole-pointer order requirement Let's add an admonition explaining that bytes with provenance must form a complete pointer in the correct order. --- src/const_eval.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/const_eval.md b/src/const_eval.md index edbddbb9fe..ee48b9e532 100644 --- a/src/const_eval.md +++ b/src/const_eval.md @@ -260,6 +260,11 @@ const _: Pair = unsafe { }; ``` +> [!NOTE] +> The bytes with provenance must form a complete pointer in the correct order. In the example above, the pointer is written at offset 20, but a pointer requires 8 bytes (assuming an 8-byte pointer). Four of those bytes fit in the `y` field; the rest extend into the padding at offset 24. When the fields are initialized, the `y` bytes get overwritten, leaving only a partial pointer (4 bytes) in the padding. These 4 bytes have provenance but don't form a complete pointer, causing compilation to fail. +> +> This restriction ensures that any bytes with provenance in the final value represent complete pointers. Binary formats such as ELF cannot represent pointer fragments, so the compiler cannot emit them in the final binary. + > [!NOTE] > 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. > From 8fd2d3ee0b331686c9a671aa73dc0bb71614750f Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 23:14:26 +0000 Subject: [PATCH 5/6] Add example of reversed pointer bytes being rejected Let's add an example demonstrating that reversing the order of pointer bytes causes compilation to fail, even though all bytes are present. The compiler tracks the position of each byte within its original pointer and only accepts pointers when reassembled in the correct order. This example copies a pointer byte-by-byte in reverse order into the padding of a struct, which fails because the fragment indices don't match up to form a valid pointer. Context: - https://github.com/rust-lang/rust/pull/144081 --- src/const_eval.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/const_eval.md b/src/const_eval.md index ee48b9e532..c0114cb897 100644 --- a/src/const_eval.md +++ b/src/const_eval.md @@ -264,6 +264,28 @@ const _: Pair = unsafe { > The bytes with provenance must form a complete pointer in the correct order. In the example above, the pointer is written at offset 20, but a pointer requires 8 bytes (assuming an 8-byte pointer). Four of those bytes fit in the `y` field; the rest extend into the padding at offset 24. When the fields are initialized, the `y` bytes get overwritten, leaving only a partial pointer (4 bytes) in the padding. These 4 bytes have provenance but don't form a complete pointer, causing compilation to fail. > > This restriction ensures that any bytes with provenance in the final value represent complete pointers. Binary formats such as ELF cannot represent pointer fragments, so the compiler cannot emit them in the final binary. +> +> Reversing the order of the pointer bytes also causes compilation to fail, even though all bytes are present: +> +> ```rust,compile_fail +> # use core::mem::{self, MaybeUninit}; +> const PTR_WIDTH: usize = mem::size_of::<*const u8>(); +> const _: MaybeUninit = unsafe { +> // ^^^^^^^^^^^^^^^^ +> // ERROR: Partial pointer in final value of constant. +> let mut m = MaybeUninit::::uninit(); +> let ptr: *const u8 = &0; +> let ptr_bytes = &raw const ptr as *const MaybeUninit; +> // Write pointer bytes in reverse order. +> let dst: *mut MaybeUninit = m.as_mut_ptr().cast(); +> let mut i = 0; +> while i < PTR_WIDTH { +> dst.add(i).write(ptr_bytes.add(PTR_WIDTH - 1 - i).read()); +> i += 1; +> } +> m +> }; +> ``` > [!NOTE] > 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. From 57f47084d314b2edd05da11219a4ae77bbe14395 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 23:16:59 +0000 Subject: [PATCH 6/6] Add normative mention about ordering pointer bytes In const eval, the bytes with provenance that become part of the final value must form a whole-pointer group with the bytes in the correct order. We have admonitions about this; let's also add disambiguating text to the normative language. --- src/const_eval.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/const_eval.md b/src/const_eval.md index c0114cb897..5ed6cf2ce3 100644 --- a/src/const_eval.md +++ b/src/const_eval.md @@ -235,7 +235,7 @@ r[const-eval.const-expr.if-match] ## Const initializers r[const-eval.const-expr.final-value-provenance] -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. +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 correctly-ordered bytes that form an entire pointer, compilation will fail. ```rust,compile_fail # use core::mem::MaybeUninit;