Skip to content

Commit 60f0a43

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 SKIP_CARGO_SEMVER_CHECKS=1
1 parent 664b976 commit 60f0a43

15 files changed

+759
-191
lines changed

.github/workflows/ci.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ jobs:
244244

245245
steps:
246246
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
247+
with:
248+
fetch-depth: 2
247249

248250
- name: Populate cache
249251
uses: ./.github/actions/cache
@@ -490,7 +492,7 @@ jobs:
490492
491493
if [ "${{ github.event_name }}" == "pull_request" ]; then
492494
# Invoked from a PR - get the PR body directly
493-
MESSAGE="${{ github.event.pull_request.body }}"
495+
MESSAGE="$(git log -1 --pretty=%B ${{ github.event.pull_request.head.sha }})"
494496
else
495497
# Invoked from the merge queue - get the commit message
496498
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! {
@@ -632,14 +634,14 @@ safety_comment! {
632634
/// SAFETY:
633635
/// `TryFromBytes` (with no validator), `FromZeros`, `FromBytes`:
634636
/// `MaybeUninit<T>` has no restrictions on its contents.
635-
unsafe_impl!(T => TryFromBytes for MaybeUninit<T>);
636-
unsafe_impl!(T => FromZeros for MaybeUninit<T>);
637-
unsafe_impl!(T => FromBytes for MaybeUninit<T>);
637+
unsafe_impl!(T => TryFromBytes for CoreMaybeUninit<T>);
638+
unsafe_impl!(T => FromZeros for CoreMaybeUninit<T>);
639+
unsafe_impl!(T => FromBytes for CoreMaybeUninit<T>);
638640
}
639641

640-
impl_for_transparent_wrapper!(T: Immutable => Immutable for MaybeUninit<T>);
641-
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for MaybeUninit<T>);
642-
assert_unaligned!(MaybeUninit<()>, MaybeUninit<u8>);
642+
impl_for_transparent_wrapper!(T: Immutable => Immutable for CoreMaybeUninit<T>);
643+
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for CoreMaybeUninit<T>);
644+
assert_unaligned!(CoreMaybeUninit<()>, CoreMaybeUninit<u8>);
643645

644646
impl_for_transparent_wrapper!(T: ?Sized + Immutable => Immutable for ManuallyDrop<T>);
645647
impl_for_transparent_wrapper!(T: ?Sized + TryFromBytes => TryFromBytes for ManuallyDrop<T>);
@@ -1257,8 +1259,8 @@ mod tests {
12571259
ManuallyDrop<UnsafeCell<()>>,
12581260
ManuallyDrop<[UnsafeCell<u8>]>,
12591261
ManuallyDrop<[UnsafeCell<bool>]>,
1260-
MaybeUninit<NotZerocopy>,
1261-
MaybeUninit<UnsafeCell<()>>,
1262+
CoreMaybeUninit<NotZerocopy>,
1263+
CoreMaybeUninit<UnsafeCell<()>>,
12621264
Wrapping<UnsafeCell<()>>
12631265
);
12641266

@@ -1300,9 +1302,9 @@ mod tests {
13001302
Option<FnManyArgs>,
13011303
Option<extern "C" fn()>,
13021304
Option<ECFnManyArgs>,
1303-
MaybeUninit<u8>,
1304-
MaybeUninit<NotZerocopy>,
1305-
MaybeUninit<UnsafeCell<()>>,
1305+
CoreMaybeUninit<u8>,
1306+
CoreMaybeUninit<NotZerocopy>,
1307+
CoreMaybeUninit<UnsafeCell<()>>,
13061308
ManuallyDrop<UnsafeCell<()>>,
13071309
ManuallyDrop<[UnsafeCell<u8>]>,
13081310
ManuallyDrop<[UnsafeCell<bool>]>,
@@ -1764,9 +1766,9 @@ mod tests {
17641766
assert_impls!(ManuallyDrop<[UnsafeCell<u8>]>: KnownLayout, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned, !Immutable);
17651767
assert_impls!(ManuallyDrop<[UnsafeCell<bool>]>: KnownLayout, TryFromBytes, FromZeros, IntoBytes, Unaligned, !Immutable, !FromBytes);
17661768

1767-
assert_impls!(MaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
1768-
assert_impls!(MaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
1769-
assert_impls!(MaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);
1769+
assert_impls!(CoreMaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
1770+
assert_impls!(CoreMaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
1771+
assert_impls!(CoreMaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);
17701772

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

src/lib.rs

+68-74
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,
@@ -732,6 +732,15 @@ pub unsafe trait KnownLayout {
732732
/// This is `()` for sized types and `usize` for slice DSTs.
733733
type PointerMetadata: PointerMetadata;
734734

735+
/// A maybe-uninitialized analog of `Self`
736+
///
737+
/// # Safety
738+
///
739+
/// `Self::LAYOUT` and `Self::MaybeUninit::LAYOUT` are identical.
740+
/// `Self::MaybeUninit` admits uninitialized bytes in all positions.
741+
#[doc(hidden)]
742+
type MaybeUninit: ?Sized + KnownLayout<PointerMetadata = Self::PointerMetadata>;
743+
735744
/// The layout of `Self`.
736745
///
737746
/// # Safety
@@ -864,6 +873,35 @@ unsafe impl<T> KnownLayout for [T] {
864873

865874
type PointerMetadata = usize;
866875

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

869907
// SAFETY: `.cast` preserves address and provenance. The returned pointer
@@ -916,9 +954,11 @@ impl_known_layout!(
916954
T => Option<T>,
917955
T: ?Sized => PhantomData<T>,
918956
T => Wrapping<T>,
919-
T => MaybeUninit<T>,
957+
T => CoreMaybeUninit<T>,
920958
T: ?Sized => *const T,
921-
T: ?Sized => *mut T
959+
T: ?Sized => *mut T,
960+
T: ?Sized => &'_ T,
961+
T: ?Sized => &'_ mut T,
922962
);
923963
impl_known_layout!(const N: usize, T => [T; N]);
924964

@@ -949,6 +989,21 @@ safety_comment! {
949989
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] UnsafeCell<T>);
950990
}
951991

992+
safety_comment! {
993+
/// SAFETY:
994+
/// - By consequence of the invariant on `T::MaybeUninit` that `T::LAYOUT`
995+
/// and `T::MaybeUninit::LAYOUT` are equal, `T` and `T::MaybeUninit`
996+
/// have the same:
997+
/// - Fixed prefix size
998+
/// - Alignment
999+
/// - (For DSTs) trailing slice element size
1000+
/// - By consequence of the above, referents `T::MaybeUninit` and `T` have
1001+
/// the require the same kind of pointer metadata, and thus it is valid to
1002+
/// perform an `as` cast from `*mut T` and `*mut T::MaybeUninit`, and this
1003+
/// operation preserves referent size (ie, `size_of_val_raw`).
1004+
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T::MaybeUninit)] MaybeUninit<T>);
1005+
}
1006+
9521007
/// Analyzes whether a type is [`FromZeros`].
9531008
///
9541009
/// This derive analyzes, at compile time, whether the annotated type satisfies
@@ -2550,7 +2605,7 @@ pub unsafe trait TryFromBytes {
25502605
where
25512606
Self: Sized,
25522607
{
2553-
let candidate = match MaybeUninit::<Self>::read_from_bytes(source) {
2608+
let candidate = match CoreMaybeUninit::<Self>::read_from_bytes(source) {
25542609
Ok(candidate) => candidate,
25552610
Err(e) => {
25562611
return Err(TryReadError::Size(e.with_dst()));
@@ -2611,7 +2666,7 @@ pub unsafe trait TryFromBytes {
26112666
where
26122667
Self: Sized,
26132668
{
2614-
let (candidate, suffix) = match MaybeUninit::<Self>::read_from_prefix(source) {
2669+
let (candidate, suffix) = match CoreMaybeUninit::<Self>::read_from_prefix(source) {
26152670
Ok(candidate) => candidate,
26162671
Err(e) => {
26172672
return Err(TryReadError::Size(e.with_dst()));
@@ -2673,7 +2728,7 @@ pub unsafe trait TryFromBytes {
26732728
where
26742729
Self: Sized,
26752730
{
2676-
let (prefix, candidate) = match MaybeUninit::<Self>::read_from_suffix(source) {
2731+
let (prefix, candidate) = match CoreMaybeUninit::<Self>::read_from_suffix(source) {
26772732
Ok(candidate) => candidate,
26782733
Err(e) => {
26792734
return Err(TryReadError::Size(e.with_dst()));
@@ -2746,7 +2801,7 @@ fn swap<T, U>((t, u): (T, U)) -> (U, T) {
27462801
#[inline(always)]
27472802
unsafe fn try_read_from<S, T: TryFromBytes>(
27482803
source: S,
2749-
mut candidate: MaybeUninit<T>,
2804+
mut candidate: CoreMaybeUninit<T>,
27502805
) -> Result<T, TryReadError<S, T>> {
27512806
// We use `from_mut` despite not mutating via `c_ptr` so that we don't need
27522807
// to add a `T: Immutable` bound.
@@ -3035,72 +3090,11 @@ pub unsafe trait FromZeros: TryFromBytes {
30353090
where
30363091
Self: KnownLayout<PointerMetadata = usize>,
30373092
{
3038-
let size = match count.size_for_metadata(Self::LAYOUT) {
3039-
Some(size) => size,
3040-
None => return Err(AllocError),
3041-
};
3042-
3043-
let align = Self::LAYOUT.align.get();
3044-
// On stable Rust versions <= 1.64.0, `Layout::from_size_align` has a
3045-
// bug in which sufficiently-large allocations (those which, when
3046-
// rounded up to the alignment, overflow `isize`) are not rejected,
3047-
// which can cause undefined behavior. See #64 for details.
3048-
//
3049-
// TODO(#67): Once our MSRV is > 1.64.0, remove this assertion.
3050-
#[allow(clippy::as_conversions)]
3051-
let max_alloc = (isize::MAX as usize).saturating_sub(align);
3052-
if size > max_alloc {
3053-
return Err(AllocError);
3054-
}
3055-
3056-
// TODO(https://github.com/rust-lang/rust/issues/55724): Use
3057-
// `Layout::repeat` once it's stabilized.
3058-
let layout = Layout::from_size_align(size, align).or(Err(AllocError))?;
3059-
3060-
let ptr = if layout.size() != 0 {
3061-
// TODO(#429): Add a "SAFETY" comment and remove this `allow`.
3062-
#[allow(clippy::undocumented_unsafe_blocks)]
3063-
let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
3064-
match NonNull::new(ptr) {
3065-
Some(ptr) => ptr,
3066-
None => return Err(AllocError),
3067-
}
3068-
} else {
3069-
let align = Self::LAYOUT.align.get();
3070-
// We use `transmute` instead of an `as` cast since Miri (with
3071-
// strict provenance enabled) notices and complains that an `as`
3072-
// cast creates a pointer with no provenance. Miri isn't smart
3073-
// enough to realize that we're only executing this branch when
3074-
// we're constructing a zero-sized `Box`, which doesn't require
3075-
// provenance.
3076-
//
3077-
// SAFETY: any initialized bit sequence is a bit-valid `*mut u8`.
3078-
// All bits of a `usize` are initialized.
3079-
#[allow(clippy::useless_transmute)]
3080-
let dangling = unsafe { mem::transmute::<usize, *mut u8>(align) };
3081-
// SAFETY: `dangling` is constructed from `Self::LAYOUT.align`,
3082-
// which is a `NonZeroUsize`, which is guaranteed to be non-zero.
3083-
//
3084-
// `Box<[T]>` does not allocate when `T` is zero-sized or when `len`
3085-
// is zero, but it does require a non-null dangling pointer for its
3086-
// allocation.
3087-
//
3088-
// TODO(https://github.com/rust-lang/rust/issues/95228): Use
3089-
// `std::ptr::without_provenance` once it's stable. That may
3090-
// optimize better. As written, Rust may assume that this consumes
3091-
// "exposed" provenance, and thus Rust may have to assume that this
3092-
// may consume provenance from any pointer whose provenance has been
3093-
// exposed.
3094-
unsafe { NonNull::new_unchecked(dangling) }
3095-
};
3096-
3097-
let ptr = Self::raw_from_ptr_len(ptr, count);
3098-
3099-
// TODO(#429): Add a "SAFETY" comment and remove this `allow`. Make sure
3100-
// to include a justification that `ptr.as_ptr()` is validly-aligned in
3101-
// the ZST case (in which we manually construct a dangling pointer).
3102-
#[allow(clippy::undocumented_unsafe_blocks)]
3103-
Ok(unsafe { Box::from_raw(ptr.as_ptr()) })
3093+
// SAFETY: `alloc::alloc::alloc_zeroed` is a valid argument of
3094+
// `new_box`. The referent of the pointer returned by `alloc_zeroed`
3095+
// (and, consequently, the `Box` derived from it) is a valid instance of
3096+
// `Self`, because `Self` is `FromZeros`.
3097+
unsafe { crate::util::new_box(count, alloc::alloc::alloc_zeroed) }
31043098
}
31053099

31063100
#[deprecated(since = "0.8.0", note = "renamed to `FromZeros::new_box_zeroed_with_elems`")]
@@ -4562,7 +4556,7 @@ pub unsafe trait FromBytes: FromZeros {
45624556
Self: Sized,
45634557
R: io::Read,
45644558
{
4565-
let mut buf = MaybeUninit::<Self>::zeroed();
4559+
let mut buf = CoreMaybeUninit::<Self>::zeroed();
45664560
let ptr = Ptr::from_mut(&mut buf);
45674561
// SAFETY: `buf` consists entirely of initialized, zeroed bytes.
45684562
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };

src/util/macros.rs

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

558558
type PointerMetadata = ();
559559

560+
// SAFETY: `CoreMaybeUninit<T>::LAYOUT` and `T::LAYOUT` are
561+
// identical because `CoreMaybeUninit<T>` has the same size and
562+
// alignment as `T` [1], and `CoreMaybeUninit` admits
563+
// uninitialized bytes in all positions.
564+
//
565+
// [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1:
566+
//
567+
// `MaybeUninit<T>` is guaranteed to have the same size,
568+
// alignment, and ABI as `T`
569+
type MaybeUninit = core::mem::MaybeUninit<Self>;
570+
560571
const LAYOUT: crate::DstLayout = crate::DstLayout::for_type::<$ty>();
561572

562573
// SAFETY: `.cast` preserves address and provenance.
@@ -599,6 +610,7 @@ macro_rules! unsafe_impl_known_layout {
599610
fn only_derive_is_allowed_to_implement_this_trait() {}
600611

601612
type PointerMetadata = <$repr as KnownLayout>::PointerMetadata;
613+
type MaybeUninit = <$repr as KnownLayout>::MaybeUninit;
602614

603615
const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT;
604616

0 commit comments

Comments
 (0)