Skip to content

Commit 96b54d1

Browse files
committed
Add initial support for unsized MaybeUninit wrapper type
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
1 parent e3b2ec1 commit 96b54d1

File tree

7 files changed

+650
-144
lines changed

7 files changed

+650
-144
lines changed

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! {
@@ -628,14 +630,14 @@ safety_comment! {
628630
/// SAFETY:
629631
/// `TryFromBytes` (with no validator), `FromZeros`, `FromBytes`:
630632
/// `MaybeUninit<T>` has no restrictions on its contents.
631-
unsafe_impl!(T => TryFromBytes for MaybeUninit<T>);
632-
unsafe_impl!(T => FromZeros for MaybeUninit<T>);
633-
unsafe_impl!(T => FromBytes for MaybeUninit<T>);
633+
unsafe_impl!(T => TryFromBytes for CoreMaybeUninit<T>);
634+
unsafe_impl!(T => FromZeros for CoreMaybeUninit<T>);
635+
unsafe_impl!(T => FromBytes for CoreMaybeUninit<T>);
634636
}
635637

636-
impl_for_transparent_wrapper!(T: Immutable => Immutable for MaybeUninit<T>);
637-
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for MaybeUninit<T>);
638-
assert_unaligned!(MaybeUninit<()>, MaybeUninit<u8>);
638+
impl_for_transparent_wrapper!(T: Immutable => Immutable for CoreMaybeUninit<T>);
639+
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for CoreMaybeUninit<T>);
640+
assert_unaligned!(CoreMaybeUninit<()>, CoreMaybeUninit<u8>);
639641

640642
impl_for_transparent_wrapper!(T: ?Sized + Immutable => Immutable for ManuallyDrop<T>);
641643
impl_for_transparent_wrapper!(T: ?Sized + TryFromBytes => TryFromBytes for ManuallyDrop<T>);
@@ -1253,8 +1255,8 @@ mod tests {
12531255
ManuallyDrop<UnsafeCell<()>>,
12541256
ManuallyDrop<[UnsafeCell<u8>]>,
12551257
ManuallyDrop<[UnsafeCell<bool>]>,
1256-
MaybeUninit<NotZerocopy>,
1257-
MaybeUninit<UnsafeCell<()>>,
1258+
CoreMaybeUninit<NotZerocopy>,
1259+
CoreMaybeUninit<UnsafeCell<()>>,
12581260
Wrapping<UnsafeCell<()>>
12591261
);
12601262

@@ -1296,9 +1298,9 @@ mod tests {
12961298
Option<FnManyArgs>,
12971299
Option<extern "C" fn()>,
12981300
Option<ECFnManyArgs>,
1299-
MaybeUninit<u8>,
1300-
MaybeUninit<NotZerocopy>,
1301-
MaybeUninit<UnsafeCell<()>>,
1301+
CoreMaybeUninit<u8>,
1302+
CoreMaybeUninit<NotZerocopy>,
1303+
CoreMaybeUninit<UnsafeCell<()>>,
13021304
ManuallyDrop<UnsafeCell<()>>,
13031305
ManuallyDrop<[UnsafeCell<u8>]>,
13041306
ManuallyDrop<[UnsafeCell<bool>]>,
@@ -1760,9 +1762,9 @@ mod tests {
17601762
assert_impls!(ManuallyDrop<[UnsafeCell<u8>]>: KnownLayout, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned, !Immutable);
17611763
assert_impls!(ManuallyDrop<[UnsafeCell<bool>]>: KnownLayout, TryFromBytes, FromZeros, IntoBytes, Unaligned, !Immutable, !FromBytes);
17621764

1763-
assert_impls!(MaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
1764-
assert_impls!(MaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
1765-
assert_impls!(MaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);
1765+
assert_impls!(CoreMaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
1766+
assert_impls!(CoreMaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
1767+
assert_impls!(CoreMaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);
17661768

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

src/lib.rs

+62-73
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ use core::{
349349
fmt::{self, Debug, Display, Formatter},
350350
hash::Hasher,
351351
marker::PhantomData,
352-
mem::{self, ManuallyDrop, MaybeUninit},
352+
mem::{self, ManuallyDrop, MaybeUninit as CoreMaybeUninit},
353353
num::{
354354
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
355355
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
@@ -727,6 +727,14 @@ pub unsafe trait KnownLayout {
727727
/// This is `()` for sized types and `usize` for slice DSTs.
728728
type PointerMetadata: PointerMetadata;
729729

730+
/// A maybe-uninitialized analog of `Self`
731+
///
732+
/// # Safety
733+
///
734+
/// `Self::LAYOUT` and `Self::MaybeUninit::LAYOUT` are identical.
735+
#[doc(hidden)]
736+
type MaybeUninit: ?Sized + KnownLayout<PointerMetadata = Self::PointerMetadata>;
737+
730738
/// The layout of `Self`.
731739
///
732740
/// # Safety
@@ -859,6 +867,35 @@ unsafe impl<T> KnownLayout for [T] {
859867

860868
type PointerMetadata = usize;
861869

870+
// SAFETY: `CoreMaybeUninit<T>::LAYOUT` and `T::LAYOUT` are identical
871+
// because `CoreMaybeUninit<T>` has the same size and alignment as `T` [1].
872+
// Consequently, `[CoreMaybeUninit<T>]::LAYOUT` and `[T]::LAYOUT` are
873+
// identical, because they both lack a fixed-sized prefix and because they
874+
// inherit the alignments of their inner element type (which are identical)
875+
// [2][3].
876+
//
877+
// `[CoreMaybeUninit<T>]` admits uninitialized bytes at all positions
878+
// because `CoreMaybeUninit<T>` admits uninitialized bytes at all positions
879+
// and because the inner elements of `[CoreMaybeUninit<T>]` are laid out
880+
// back-to-back [2][3].
881+
//
882+
// [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1:
883+
//
884+
// `MaybeUninit<T>` is guaranteed to have the same size, alignment, and ABI as
885+
// `T`
886+
//
887+
// [2] Per https://doc.rust-lang.org/1.82.0/reference/type-layout.html#slice-layout:
888+
//
889+
// Slices have the same layout as the section of the array they slice.
890+
//
891+
// [3] Per https://doc.rust-lang.org/1.82.0/reference/type-layout.html#array-layout:
892+
//
893+
// An array of `[T; N]` has a size of `size_of::<T>() * N` and the same
894+
// alignment of `T`. Arrays are laid out so that the zero-based `nth`
895+
// element of the array is offset from the start of the array by `n *
896+
// size_of::<T>()` bytes.
897+
type MaybeUninit = [CoreMaybeUninit<T>];
898+
862899
const LAYOUT: DstLayout = DstLayout::for_slice::<T>();
863900

864901
// SAFETY: `.cast` preserves address and provenance. The returned pointer
@@ -911,7 +948,7 @@ impl_known_layout!(
911948
T => Option<T>,
912949
T: ?Sized => PhantomData<T>,
913950
T => Wrapping<T>,
914-
T => MaybeUninit<T>,
951+
T => CoreMaybeUninit<T>,
915952
T: ?Sized => *const T,
916953
T: ?Sized => *mut T
917954
);
@@ -944,6 +981,21 @@ safety_comment! {
944981
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] UnsafeCell<T>);
945982
}
946983

984+
safety_comment! {
985+
/// SAFETY:
986+
/// - By consequence of the invariant on `T::MaybeUninit` that `T::LAYOUT`
987+
/// and `T::MaybeUninit::LAYOUT` are equal, `T` and `T::MaybeUninit`
988+
/// have the same:
989+
/// - Fixed prefix size
990+
/// - Alignment
991+
/// - (For DSTs) trailing slice element size
992+
/// - By consequence of the above, referents `T::MaybeUninit` and `T` have
993+
/// the require the same kind of pointer metadata, and thus it is valid to
994+
/// perform an `as` cast from `*mut T` and `*mut T::MaybeUninit`, and this
995+
/// operation preserves referent size (ie, `size_of_val_raw`).
996+
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T::MaybeUninit)] MaybeUninit<T>);
997+
}
998+
947999
/// Analyzes whether a type is [`FromZeros`].
9481000
///
9491001
/// This derive analyzes, at compile time, whether the annotated type satisfies
@@ -2545,7 +2597,7 @@ pub unsafe trait TryFromBytes {
25452597
where
25462598
Self: Sized,
25472599
{
2548-
let candidate = match MaybeUninit::<Self>::read_from_bytes(source) {
2600+
let candidate = match CoreMaybeUninit::<Self>::read_from_bytes(source) {
25492601
Ok(candidate) => candidate,
25502602
Err(e) => {
25512603
return Err(TryReadError::Size(e.with_dst()));
@@ -2606,7 +2658,7 @@ pub unsafe trait TryFromBytes {
26062658
where
26072659
Self: Sized,
26082660
{
2609-
let (candidate, suffix) = match MaybeUninit::<Self>::read_from_prefix(source) {
2661+
let (candidate, suffix) = match CoreMaybeUninit::<Self>::read_from_prefix(source) {
26102662
Ok(candidate) => candidate,
26112663
Err(e) => {
26122664
return Err(TryReadError::Size(e.with_dst()));
@@ -2668,7 +2720,7 @@ pub unsafe trait TryFromBytes {
26682720
where
26692721
Self: Sized,
26702722
{
2671-
let (prefix, candidate) = match MaybeUninit::<Self>::read_from_suffix(source) {
2723+
let (prefix, candidate) = match CoreMaybeUninit::<Self>::read_from_suffix(source) {
26722724
Ok(candidate) => candidate,
26732725
Err(e) => {
26742726
return Err(TryReadError::Size(e.with_dst()));
@@ -2741,7 +2793,7 @@ fn swap<T, U>((t, u): (T, U)) -> (U, T) {
27412793
#[inline(always)]
27422794
unsafe fn try_read_from<S, T: TryFromBytes>(
27432795
source: S,
2744-
mut candidate: MaybeUninit<T>,
2796+
mut candidate: CoreMaybeUninit<T>,
27452797
) -> Result<T, TryReadError<S, T>> {
27462798
// We use `from_mut` despite not mutating via `c_ptr` so that we don't need
27472799
// to add a `T: Immutable` bound.
@@ -3030,72 +3082,9 @@ pub unsafe trait FromZeros: TryFromBytes {
30303082
where
30313083
Self: KnownLayout<PointerMetadata = usize>,
30323084
{
3033-
let size = match count.size_for_metadata(Self::LAYOUT) {
3034-
Some(size) => size,
3035-
None => return Err(AllocError),
3036-
};
3037-
3038-
let align = Self::LAYOUT.align.get();
3039-
// On stable Rust versions <= 1.64.0, `Layout::from_size_align` has a
3040-
// bug in which sufficiently-large allocations (those which, when
3041-
// rounded up to the alignment, overflow `isize`) are not rejected,
3042-
// which can cause undefined behavior. See #64 for details.
3043-
//
3044-
// TODO(#67): Once our MSRV is > 1.64.0, remove this assertion.
3045-
#[allow(clippy::as_conversions)]
3046-
let max_alloc = (isize::MAX as usize).saturating_sub(align);
3047-
if size > max_alloc {
3048-
return Err(AllocError);
3049-
}
3050-
3051-
// TODO(https://github.com/rust-lang/rust/issues/55724): Use
3052-
// `Layout::repeat` once it's stabilized.
3053-
let layout = Layout::from_size_align(size, align).or(Err(AllocError))?;
3054-
3055-
let ptr = if layout.size() != 0 {
3056-
// TODO(#429): Add a "SAFETY" comment and remove this `allow`.
3057-
#[allow(clippy::undocumented_unsafe_blocks)]
3058-
let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
3059-
match NonNull::new(ptr) {
3060-
Some(ptr) => ptr,
3061-
None => return Err(AllocError),
3062-
}
3063-
} else {
3064-
let align = Self::LAYOUT.align.get();
3065-
// We use `transmute` instead of an `as` cast since Miri (with
3066-
// strict provenance enabled) notices and complains that an `as`
3067-
// cast creates a pointer with no provenance. Miri isn't smart
3068-
// enough to realize that we're only executing this branch when
3069-
// we're constructing a zero-sized `Box`, which doesn't require
3070-
// provenance.
3071-
//
3072-
// SAFETY: any initialized bit sequence is a bit-valid `*mut u8`.
3073-
// All bits of a `usize` are initialized.
3074-
#[allow(clippy::useless_transmute)]
3075-
let dangling = unsafe { mem::transmute::<usize, *mut u8>(align) };
3076-
// SAFETY: `dangling` is constructed from `Self::LAYOUT.align`,
3077-
// which is a `NonZeroUsize`, which is guaranteed to be non-zero.
3078-
//
3079-
// `Box<[T]>` does not allocate when `T` is zero-sized or when `len`
3080-
// is zero, but it does require a non-null dangling pointer for its
3081-
// allocation.
3082-
//
3083-
// TODO(https://github.com/rust-lang/rust/issues/95228): Use
3084-
// `std::ptr::without_provenance` once it's stable. That may
3085-
// optimize better. As written, Rust may assume that this consumes
3086-
// "exposed" provenance, and thus Rust may have to assume that this
3087-
// may consume provenance from any pointer whose provenance has been
3088-
// exposed.
3089-
unsafe { NonNull::new_unchecked(dangling) }
3090-
};
3091-
3092-
let ptr = Self::raw_from_ptr_len(ptr, count);
3093-
3094-
// TODO(#429): Add a "SAFETY" comment and remove this `allow`. Make sure
3095-
// to include a justification that `ptr.as_ptr()` is validly-aligned in
3096-
// the ZST case (in which we manually construct a dangling pointer).
3097-
#[allow(clippy::undocumented_unsafe_blocks)]
3098-
Ok(unsafe { Box::from_raw(ptr.as_ptr()) })
3085+
// SAFETY: The referent of the pointer returned by `alloc_zeroed` is a
3086+
// valid instance of `Self`, because `Self` is `FromZeros`.
3087+
unsafe { crate::util::new_box(count, alloc::alloc::alloc_zeroed) }
30993088
}
31003089

31013090
#[deprecated(since = "0.8.0", note = "renamed to `FromZeros::new_box_zeroed_with_elems`")]
@@ -4557,7 +4546,7 @@ pub unsafe trait FromBytes: FromZeros {
45574546
Self: Sized,
45584547
R: io::Read,
45594548
{
4560-
let mut buf = MaybeUninit::<Self>::zeroed();
4549+
let mut buf = CoreMaybeUninit::<Self>::zeroed();
45614550
let ptr = Ptr::from_mut(&mut buf);
45624551
// SAFETY: `buf` consists entirely of initialized, zeroed bytes.
45634552
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };

src/util/macros.rs

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

531531
type PointerMetadata = ();
532532

533+
// SAFETY: `CoreMaybeUninit<T>::LAYOUT` and `T::LAYOUT` are
534+
// identical because `CoreMaybeUninit<T>` has the same size and
535+
// alignment as `T` [1], and `CoreMaybeUninit` admits
536+
// uninitialized bytes in all positions.
537+
//
538+
// [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1:
539+
//
540+
// `MaybeUninit<T>` is guaranteed to have the same size,
541+
// alignment, and ABI as `T`
542+
type MaybeUninit = core::mem::MaybeUninit<Self>;
543+
533544
const LAYOUT: crate::DstLayout = crate::DstLayout::for_type::<$ty>();
534545

535546
// SAFETY: `.cast` preserves address and provenance.
@@ -572,6 +583,7 @@ macro_rules! unsafe_impl_known_layout {
572583
fn only_derive_is_allowed_to_implement_this_trait() {}
573584

574585
type PointerMetadata = <$repr as KnownLayout>::PointerMetadata;
586+
type MaybeUninit = <$repr as KnownLayout>::MaybeUninit;
575587

576588
const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT;
577589

0 commit comments

Comments
 (0)