Skip to content

Commit 3acb652

Browse files
joshlfjswrenn
andauthored
Add initial support for unsized MaybeUninit wrapper type (#2055) (#2310)
This is achieved by adding a `MaybeUninit` associated type to `KnownLayout`, whose layout is identical to `Self` except that it admits uninitialized bytes in all positions. For sized types, this is bound to `mem::MaybeUninit<Self>`. For potentially unsized structs, we synthesize a doppelganger with the same `repr`, whose leading fields are wrapped in `mem::MaybeUninit` and whose trailing field is the `MaybeUninit` associated type of struct's original trailing field type. This type-level recursion bottoms out at `[T]`, whose `MaybeUninit` associated type is bound to `[mem::MaybeUninit<T>]`. Makes progress towards #1797 SKIP_CARGO_SEMVER_CHECKS=1 gherrit-pr-id: Idfc357094e28b54a15d947141241ca2da83dcc91 Co-authored-by: Jack Wrenn <jswrenn@amazon.com>
1 parent 146fd48 commit 3acb652

17 files changed

+749
-179
lines changed

.github/workflows/ci.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ jobs:
181181

182182
steps:
183183
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
184+
with:
185+
fetch-depth: 2
184186

185187
- name: Populate cache
186188
uses: ./.github/actions/cache
@@ -427,7 +429,7 @@ jobs:
427429
428430
if [ "${{ github.event_name }}" == "pull_request" ]; then
429431
# Invoked from a PR - get the PR body directly
430-
MESSAGE="${{ github.event.pull_request.body }}"
432+
MESSAGE="$(git log -1 --pretty=%B ${{ github.event.pull_request.head.sha }})"
431433
else
432434
# Invoked from the merge queue - get the commit message
433435
MESSAGE="$(git log -1 --pretty=%B ${{ github.sha }})"

src/impls.rs

+16-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// This file may not be copied, modified, or distributed except according to
88
// those terms.
99

10+
use core::mem::MaybeUninit as CoreMaybeUninit;
11+
1012
use super::*;
1113

1214
safety_comment! {
@@ -619,14 +621,14 @@ safety_comment! {
619621
/// SAFETY:
620622
/// `TryFromBytes` (with no validator), `FromZeros`, `FromBytes`:
621623
/// `MaybeUninit<T>` has no restrictions on its contents.
622-
unsafe_impl!(T => TryFromBytes for MaybeUninit<T>);
623-
unsafe_impl!(T => FromZeros for MaybeUninit<T>);
624-
unsafe_impl!(T => FromBytes for MaybeUninit<T>);
624+
unsafe_impl!(T => TryFromBytes for CoreMaybeUninit<T>);
625+
unsafe_impl!(T => FromZeros for CoreMaybeUninit<T>);
626+
unsafe_impl!(T => FromBytes for CoreMaybeUninit<T>);
625627
}
626628

627-
impl_for_transparent_wrapper!(T: Immutable => Immutable for MaybeUninit<T>);
628-
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for MaybeUninit<T>);
629-
assert_unaligned!(MaybeUninit<()>, MaybeUninit<u8>);
629+
impl_for_transparent_wrapper!(T: Immutable => Immutable for CoreMaybeUninit<T>);
630+
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for CoreMaybeUninit<T>);
631+
assert_unaligned!(CoreMaybeUninit<()>, CoreMaybeUninit<u8>);
630632

631633
impl_for_transparent_wrapper!(T: ?Sized + Immutable => Immutable for ManuallyDrop<T>);
632634
impl_for_transparent_wrapper!(T: ?Sized + TryFromBytes => TryFromBytes for ManuallyDrop<T>);
@@ -1235,8 +1237,8 @@ mod tests {
12351237
ManuallyDrop<UnsafeCell<()>>,
12361238
ManuallyDrop<[UnsafeCell<u8>]>,
12371239
ManuallyDrop<[UnsafeCell<bool>]>,
1238-
MaybeUninit<NotZerocopy>,
1239-
MaybeUninit<UnsafeCell<()>>,
1240+
CoreMaybeUninit<NotZerocopy>,
1241+
CoreMaybeUninit<UnsafeCell<()>>,
12401242
Wrapping<UnsafeCell<()>>
12411243
);
12421244

@@ -1278,9 +1280,9 @@ mod tests {
12781280
Option<FnManyArgs>,
12791281
Option<extern "C" fn()>,
12801282
Option<ECFnManyArgs>,
1281-
MaybeUninit<u8>,
1282-
MaybeUninit<NotZerocopy>,
1283-
MaybeUninit<UnsafeCell<()>>,
1283+
CoreMaybeUninit<u8>,
1284+
CoreMaybeUninit<NotZerocopy>,
1285+
CoreMaybeUninit<UnsafeCell<()>>,
12841286
ManuallyDrop<UnsafeCell<()>>,
12851287
ManuallyDrop<[UnsafeCell<u8>]>,
12861288
ManuallyDrop<[UnsafeCell<bool>]>,
@@ -1742,9 +1744,9 @@ mod tests {
17421744
assert_impls!(ManuallyDrop<[UnsafeCell<u8>]>: KnownLayout, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned, !Immutable);
17431745
assert_impls!(ManuallyDrop<[UnsafeCell<bool>]>: KnownLayout, TryFromBytes, FromZeros, IntoBytes, Unaligned, !Immutable, !FromBytes);
17441746

1745-
assert_impls!(MaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
1746-
assert_impls!(MaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
1747-
assert_impls!(MaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);
1747+
assert_impls!(CoreMaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
1748+
assert_impls!(CoreMaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
1749+
assert_impls!(CoreMaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);
17481750

17491751
assert_impls!(Wrapping<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned);
17501752
// This test is important because it allows us to test our hand-rolled

src/lib.rs

+68-62
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ use core::{
354354
fmt::{self, Debug, Display, Formatter},
355355
hash::Hasher,
356356
marker::PhantomData,
357-
mem::{self, ManuallyDrop, MaybeUninit},
357+
mem::{self, ManuallyDrop, MaybeUninit as CoreMaybeUninit},
358358
num::{
359359
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
360360
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
@@ -711,6 +711,15 @@ pub unsafe trait KnownLayout {
711711
/// This is `()` for sized types and `usize` for slice DSTs.
712712
type PointerMetadata: PointerMetadata;
713713

714+
/// A maybe-uninitialized analog of `Self`
715+
///
716+
/// # Safety
717+
///
718+
/// `Self::LAYOUT` and `Self::MaybeUninit::LAYOUT` are identical.
719+
/// `Self::MaybeUninit` admits uninitialized bytes in all positions.
720+
#[doc(hidden)]
721+
type MaybeUninit: ?Sized + KnownLayout<PointerMetadata = Self::PointerMetadata>;
722+
714723
/// The layout of `Self`.
715724
///
716725
/// # Safety
@@ -843,6 +852,35 @@ unsafe impl<T> KnownLayout for [T] {
843852

844853
type PointerMetadata = usize;
845854

855+
// SAFETY: `CoreMaybeUninit<T>::LAYOUT` and `T::LAYOUT` are identical
856+
// because `CoreMaybeUninit<T>` has the same size and alignment as `T` [1].
857+
// Consequently, `[CoreMaybeUninit<T>]::LAYOUT` and `[T]::LAYOUT` are
858+
// identical, because they both lack a fixed-sized prefix and because they
859+
// inherit the alignments of their inner element type (which are identical)
860+
// [2][3].
861+
//
862+
// `[CoreMaybeUninit<T>]` admits uninitialized bytes at all positions
863+
// because `CoreMaybeUninit<T>` admits uninitialized bytes at all positions
864+
// and because the inner elements of `[CoreMaybeUninit<T>]` are laid out
865+
// back-to-back [2][3].
866+
//
867+
// [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1:
868+
//
869+
// `MaybeUninit<T>` is guaranteed to have the same size, alignment, and ABI as
870+
// `T`
871+
//
872+
// [2] Per https://doc.rust-lang.org/1.82.0/reference/type-layout.html#slice-layout:
873+
//
874+
// Slices have the same layout as the section of the array they slice.
875+
//
876+
// [3] Per https://doc.rust-lang.org/1.82.0/reference/type-layout.html#array-layout:
877+
//
878+
// An array of `[T; N]` has a size of `size_of::<T>() * N` and the same
879+
// alignment of `T`. Arrays are laid out so that the zero-based `nth`
880+
// element of the array is offset from the start of the array by `n *
881+
// size_of::<T>()` bytes.
882+
type MaybeUninit = [CoreMaybeUninit<T>];
883+
846884
const LAYOUT: DstLayout = DstLayout::for_slice::<T>();
847885

848886
// SAFETY: `.cast` preserves address and provenance. The returned pointer
@@ -895,9 +933,11 @@ impl_known_layout!(
895933
T => Option<T>,
896934
T: ?Sized => PhantomData<T>,
897935
T => Wrapping<T>,
898-
T => MaybeUninit<T>,
936+
T => CoreMaybeUninit<T>,
899937
T: ?Sized => *const T,
900-
T: ?Sized => *mut T
938+
T: ?Sized => *mut T,
939+
T: ?Sized => &'_ T,
940+
T: ?Sized => &'_ mut T,
901941
);
902942
impl_known_layout!(const N: usize, T => [T; N]);
903943

@@ -928,6 +968,21 @@ safety_comment! {
928968
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] UnsafeCell<T>);
929969
}
930970

971+
safety_comment! {
972+
/// SAFETY:
973+
/// - By consequence of the invariant on `T::MaybeUninit` that `T::LAYOUT`
974+
/// and `T::MaybeUninit::LAYOUT` are equal, `T` and `T::MaybeUninit`
975+
/// have the same:
976+
/// - Fixed prefix size
977+
/// - Alignment
978+
/// - (For DSTs) trailing slice element size
979+
/// - By consequence of the above, referents `T::MaybeUninit` and `T` have
980+
/// the require the same kind of pointer metadata, and thus it is valid to
981+
/// perform an `as` cast from `*mut T` and `*mut T::MaybeUninit`, and this
982+
/// operation preserves referent size (ie, `size_of_val_raw`).
983+
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T::MaybeUninit)] MaybeUninit<T>);
984+
}
985+
931986
/// Analyzes whether a type is [`FromZeros`].
932987
///
933988
/// This derive analyzes, at compile time, whether the annotated type satisfies
@@ -2547,7 +2602,7 @@ pub unsafe trait TryFromBytes {
25472602
where
25482603
Self: Sized,
25492604
{
2550-
let candidate = match MaybeUninit::<Self>::read_from_bytes(source) {
2605+
let candidate = match CoreMaybeUninit::<Self>::read_from_bytes(source) {
25512606
Ok(candidate) => candidate,
25522607
Err(e) => {
25532608
return Err(TryReadError::Size(e.with_dst()));
@@ -2608,7 +2663,7 @@ pub unsafe trait TryFromBytes {
26082663
where
26092664
Self: Sized,
26102665
{
2611-
let (candidate, suffix) = match MaybeUninit::<Self>::read_from_prefix(source) {
2666+
let (candidate, suffix) = match CoreMaybeUninit::<Self>::read_from_prefix(source) {
26122667
Ok(candidate) => candidate,
26132668
Err(e) => {
26142669
return Err(TryReadError::Size(e.with_dst()));
@@ -2670,7 +2725,7 @@ pub unsafe trait TryFromBytes {
26702725
where
26712726
Self: Sized,
26722727
{
2673-
let (prefix, candidate) = match MaybeUninit::<Self>::read_from_suffix(source) {
2728+
let (prefix, candidate) = match CoreMaybeUninit::<Self>::read_from_suffix(source) {
26742729
Ok(candidate) => candidate,
26752730
Err(e) => {
26762731
return Err(TryReadError::Size(e.with_dst()));
@@ -2743,7 +2798,7 @@ fn swap<T, U>((t, u): (T, U)) -> (U, T) {
27432798
#[inline(always)]
27442799
unsafe fn try_read_from<S, T: TryFromBytes>(
27452800
source: S,
2746-
mut candidate: MaybeUninit<T>,
2801+
mut candidate: CoreMaybeUninit<T>,
27472802
) -> Result<T, TryReadError<S, T>> {
27482803
// We use `from_mut` despite not mutating via `c_ptr` so that we don't need
27492804
// to add a `T: Immutable` bound.
@@ -3032,60 +3087,11 @@ pub unsafe trait FromZeros: TryFromBytes {
30323087
where
30333088
Self: KnownLayout<PointerMetadata = usize>,
30343089
{
3035-
let size = match count.size_for_metadata(Self::LAYOUT) {
3036-
Some(size) => size,
3037-
None => return Err(AllocError),
3038-
};
3039-
3040-
let align = Self::LAYOUT.align.get();
3041-
3042-
// TODO(https://github.com/rust-lang/rust/issues/55724): Use
3043-
// `Layout::repeat` once it's stabilized.
3044-
let layout = Layout::from_size_align(size, align).or(Err(AllocError))?;
3045-
3046-
let ptr = if layout.size() != 0 {
3047-
// TODO(#429): Add a "SAFETY" comment and remove this `allow`.
3048-
#[allow(clippy::undocumented_unsafe_blocks)]
3049-
let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
3050-
match NonNull::new(ptr) {
3051-
Some(ptr) => ptr,
3052-
None => return Err(AllocError),
3053-
}
3054-
} else {
3055-
// We use `transmute` instead of an `as` cast since Miri (with
3056-
// strict provenance enabled) notices and complains that an `as`
3057-
// cast creates a pointer with no provenance. Miri isn't smart
3058-
// enough to realize that we're only executing this branch when
3059-
// we're constructing a zero-sized `Box`, which doesn't require
3060-
// provenance.
3061-
//
3062-
// SAFETY: any initialized bit sequence is a bit-valid `*mut u8`.
3063-
// All bits of a `usize` are initialized.
3064-
#[allow(clippy::useless_transmute)]
3065-
let dangling = unsafe { mem::transmute::<usize, *mut u8>(align) };
3066-
// SAFETY: `dangling` is constructed from `Self::LAYOUT.align`,
3067-
// which is a `NonZeroUsize`, which is guaranteed to be non-zero.
3068-
//
3069-
// `Box<[T]>` does not allocate when `T` is zero-sized or when `len`
3070-
// is zero, but it does require a non-null dangling pointer for its
3071-
// allocation.
3072-
//
3073-
// TODO(https://github.com/rust-lang/rust/issues/95228): Use
3074-
// `std::ptr::without_provenance` once it's stable. That may
3075-
// optimize better. As written, Rust may assume that this consumes
3076-
// "exposed" provenance, and thus Rust may have to assume that this
3077-
// may consume provenance from any pointer whose provenance has been
3078-
// exposed.
3079-
unsafe { NonNull::new_unchecked(dangling) }
3080-
};
3081-
3082-
let ptr = Self::raw_from_ptr_len(ptr, count);
3083-
3084-
// TODO(#429): Add a "SAFETY" comment and remove this `allow`. Make sure
3085-
// to include a justification that `ptr.as_ptr()` is validly-aligned in
3086-
// the ZST case (in which we manually construct a dangling pointer).
3087-
#[allow(clippy::undocumented_unsafe_blocks)]
3088-
Ok(unsafe { Box::from_raw(ptr.as_ptr()) })
3090+
// SAFETY: `alloc::alloc::alloc_zeroed` is a valid argument of
3091+
// `new_box`. The referent of the pointer returned by `alloc_zeroed`
3092+
// (and, consequently, the `Box` derived from it) is a valid instance of
3093+
// `Self`, because `Self` is `FromZeros`.
3094+
unsafe { crate::util::new_box(count, alloc::alloc::alloc_zeroed) }
30893095
}
30903096

30913097
/// Creates a `Vec<Self>` from zeroed bytes.
@@ -4532,7 +4538,7 @@ pub unsafe trait FromBytes: FromZeros {
45324538
Self: Sized,
45334539
R: io::Read,
45344540
{
4535-
let mut buf = MaybeUninit::<Self>::zeroed();
4541+
let mut buf = CoreMaybeUninit::<Self>::zeroed();
45364542
let ptr = Ptr::from_mut(&mut buf);
45374543
// SAFETY: `buf` consists entirely of initialized, zeroed bytes.
45384544
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };

src/util/macros.rs

+12
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,17 @@ macro_rules! impl_known_layout {
546546

547547
type PointerMetadata = ();
548548

549+
// SAFETY: `CoreMaybeUninit<T>::LAYOUT` and `T::LAYOUT` are
550+
// identical because `CoreMaybeUninit<T>` has the same size and
551+
// alignment as `T` [1], and `CoreMaybeUninit` admits
552+
// uninitialized bytes in all positions.
553+
//
554+
// [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1:
555+
//
556+
// `MaybeUninit<T>` is guaranteed to have the same size,
557+
// alignment, and ABI as `T`
558+
type MaybeUninit = core::mem::MaybeUninit<Self>;
559+
549560
const LAYOUT: crate::DstLayout = crate::DstLayout::for_type::<$ty>();
550561

551562
// SAFETY: `.cast` preserves address and provenance.
@@ -588,6 +599,7 @@ macro_rules! unsafe_impl_known_layout {
588599
fn only_derive_is_allowed_to_implement_this_trait() {}
589600

590601
type PointerMetadata = <$repr as KnownLayout>::PointerMetadata;
602+
type MaybeUninit = <$repr as KnownLayout>::MaybeUninit;
591603

592604
const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT;
593605

0 commit comments

Comments
 (0)