Skip to content

Commit 8847baa

Browse files
committed
WIP
1 parent b555bf2 commit 8847baa

File tree

1 file changed

+55
-0
lines changed

1 file changed

+55
-0
lines changed

src/const_eval.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,63 @@ 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)]
244+
struct Pair {
245+
x: u128,
246+
y: MaybeUninit<u64>,
247+
// 8 bytes of padding at offset 24.
248+
}
249+
250+
const C: Pair = unsafe {
251+
let mut m = MaybeUninit::<Pair>::uninit();
252+
// Store pointer that extends half-way into trailing padding.
253+
m.as_mut_ptr().byte_add(20).cast::<&u8>().write_unaligned(&0);
254+
// Initialize fields.
255+
(*m.as_mut_ptr()).x = 0;
256+
(*m.as_mut_ptr()).y = MaybeUninit::new(0);
257+
// Now `m` contains a pointer fragment in the padding.
258+
m.assume_init()
259+
};
260+
```
261+
262+
> [!NOTE]
263+
> Manually initializing (e.g., zeroing) the padding bytes ensures the final value is accepted:
264+
>
265+
> ```rust
266+
# use std::mem::MaybeUninit;
267+
# #[repr(C)]
268+
# struct Pair {
269+
# x: u128,
270+
# y: MaybeUninit<u64>,
271+
# }
272+
const C: Pair = unsafe {
273+
let mut m = MaybeUninit::<Pair>::uninit();
274+
m.as_mut_ptr().byte_add(20).cast::<&u8>().write_unaligned(&0);
275+
// Explicitly zero the padding.
276+
m.as_mut_ptr().byte_add(24).cast::<u64>().write_unaligned(0);
277+
// As above.
278+
(*m.as_mut_ptr()).x = 0;
279+
(*m.as_mut_ptr()).y = MaybeUninit::new(0);
280+
m.assume_init()
281+
};
282+
> ```
283+
240284
> [!NOTE]
241285
> 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.
286+
>
287+
> E.g., `rustc` currently accepts this, even though the padding bytes are uninitialized:
288+
>
289+
> ```rust
290+
> # #[repr(C)]
291+
> # struct Pair { x: u128, y: u64 }
292+
> // The padding bytes are uninitialized.
293+
> const ALLOWED: Pair = Pair { x: 0, y: 0 };
294+
> ```
295+
>
296+
> 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). Because the semantics of typed copies are not yet fully defined in Rust --- and to preserve the ability to change how they work in the future (for example, to always set the padding bytes to uninitialized) --- the language allows the compiler to reject any initializer with an uninitialized padding byte. Since the compiler cannot currently guarantee that an uninitialized byte does not contain a pointer fragment without a full model of typed copies, this allowance is necessary to avoid relying on underspecified details of the language.
242297
243298
r[const-eval.const-context]
244299
## Const context

0 commit comments

Comments
 (0)