Skip to content

Commit 7ddaeb4

Browse files
committed
Add MaybeUninit type
The standard library's `MaybeUninit` type does not currently support wrapping unsized types. This commit introduces a polyfill with the same behavior as `MaybeUninit` which does support wrapping unsized types. In this commit, the only supported types are sized types and slice types. Later (as part of #29), we will add the ability to derive the `AsMaybeUninit` trait, which will extend support to custom DSTs. TODO: Figure out how to get rid of KnownLayout<MaybeUninit = mem::MaybeUninit<T>> bounds. Makes progress on #29
1 parent 6de0dfd commit 7ddaeb4

File tree

3 files changed

+482
-15
lines changed

3 files changed

+482
-15
lines changed

src/lib.rs

+187-11
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ use core::{
177177
fmt::{self, Debug, Display, Formatter},
178178
hash::Hasher,
179179
marker::PhantomData,
180-
mem::{self, ManuallyDrop, MaybeUninit},
180+
mem::{self, ManuallyDrop},
181181
num::{
182182
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
183183
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
@@ -229,6 +229,31 @@ pub unsafe trait KnownLayout: sealed::KnownLayoutSealed {
229229
#[doc(hidden)]
230230
const TRAILING_SLICE_ELEM_SIZE: Option<usize>;
231231

232+
/// A type which has the same layout as `Self`, but which has no validity
233+
/// constraints.
234+
///
235+
/// Roughly speaking, this type is equivalent to what the standard library's
236+
/// [`MaybeUninit<Self>`] would be if it supported unsized types.
237+
///
238+
/// # Safety
239+
///
240+
/// For `T: KnownLayout`, the following must hold:
241+
/// - Given `m: T::MaybeUninit`, it is sound to write any byte value,
242+
/// including an uninitialized byte, at any byte offset in `m`
243+
/// - `T` and `T::MaybeUninit` have the same alignment requirement
244+
/// - It is valid to use an `as` cast to convert a `t: *const T` to a `m:
245+
/// *const T::MaybeUninit` and vice-versa (and likewise for `*mut T`/`*mut
246+
/// T::MaybeUninit`). Regardless of which direction the conversion was
247+
/// performed, the sizes of the pointers' referents are always equal (in
248+
/// terms of an API which is not yet stable, `size_of_val_raw(t) ==
249+
/// size_of_val_raw(m)`).
250+
/// - `T::MaybeUninit` contains [`UnsafeCell`]s at exactly the same byte
251+
/// ranges that `T` does.
252+
///
253+
/// [`MaybeUninit<Self>`]: core::mem::MaybeUninit
254+
/// [`UnsafeCell`]: core::cell::UnsafeCell
255+
type MaybeUninit: ?Sized + KnownLayout;
256+
232257
/// Validates that the memory region at `addr` of length `bytes_len`
233258
/// satisfies `Self`'s size and alignment requirements, returning `(elems,
234259
/// split_at, prefix_suffix_bytes)`.
@@ -302,6 +327,24 @@ pub unsafe trait KnownLayout: sealed::KnownLayoutSealed {
302327
/// elements in its trailing slice.
303328
#[doc(hidden)]
304329
fn raw_from_ptr_len(bytes: NonNull<u8>, elems: usize) -> NonNull<Self>;
330+
331+
/// Converts a pointer at the type level.
332+
///
333+
/// # Safety
334+
///
335+
/// Callers may assume that the memory region addressed by the return value
336+
/// is the same as that addressed by the argument, and that both the return
337+
/// value and the argument have the same provenance.
338+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<Self::MaybeUninit>) -> NonNull<Self>;
339+
340+
/// Converts a pointer at the type level.
341+
///
342+
/// # Safety
343+
///
344+
/// Callers may assume that the memory region addressed by the return value
345+
/// is the same as that addressed by the argument, and that both the return
346+
/// value and the argument have the same provenance.
347+
fn cast_to_maybe_uninit(slf: NonNull<Self>) -> NonNull<Self::MaybeUninit>;
305348
}
306349

307350
impl<T: KnownLayout> sealed::KnownLayoutSealed for [T] {}
@@ -321,6 +364,22 @@ unsafe impl<T: KnownLayout> KnownLayout for [T] {
321364
};
322365
const TRAILING_SLICE_ELEM_SIZE: Option<usize> = Some(mem::size_of::<T>());
323366

367+
// SAFETY:
368+
// - `MaybeUninit` has no bit validity requirements and `[U]` has the same
369+
// bit validity requirements as `U`, so `[MaybeUninit<T>]` has no bit
370+
// validity requirements. Thus, it is sound to write any byte value,
371+
// including an uninitialized byte, at any byte offset.
372+
// - Since `MaybeUninit<T>` has the same layout as `T`, and `[U]` has the
373+
// same alignment as `U`, `[MaybeUninit<T>]` has the same alignment as
374+
// `[T]`.
375+
// - `[T]` and `[MaybeUninit<T>]` are both slice types, and so pointers can
376+
// be converted using an `as` cast. Since `T` and `MaybeUninit<T>` have
377+
// the same size, and since such a cast preserves the number of elements
378+
// in the slice, the referent slices themselves will have the same size.
379+
// - `MaybeUninit<T>` has the same field offsets as `[T]`, and so it
380+
// contains `UnsafeCell`s at exactly the same byte ranges as `[T]`.
381+
type MaybeUninit = [mem::MaybeUninit<T>];
382+
324383
// SAFETY: `.cast` preserves address and provenance. The returned pointer
325384
// refers to an object with `elems` elements by construction.
326385
#[inline(always)]
@@ -329,6 +388,20 @@ unsafe impl<T: KnownLayout> KnownLayout for [T] {
329388
#[allow(unstable_name_collisions)]
330389
NonNull::slice_from_raw_parts(data.cast::<T>(), elems)
331390
}
391+
392+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<[mem::MaybeUninit<T>]>) -> NonNull<[T]> {
393+
let (ptr, len) = (maybe_uninit.cast::<T>(), maybe_uninit.len());
394+
// TODO(#67): Remove this allow. See NonNullExt for more details.
395+
#[allow(unstable_name_collisions)]
396+
NonNull::slice_from_raw_parts(ptr, len)
397+
}
398+
399+
fn cast_to_maybe_uninit(slf: NonNull<[T]>) -> NonNull<[mem::MaybeUninit<T>]> {
400+
let (ptr, len) = (slf.cast::<mem::MaybeUninit<T>>(), slf.len());
401+
// TODO(#67): Remove this allow. See NonNullExt for more details.
402+
#[allow(unstable_name_collisions)]
403+
NonNull::slice_from_raw_parts(ptr, len)
404+
}
332405
}
333406

334407
/// Implements `KnownLayout` for a sized type.
@@ -365,11 +438,36 @@ macro_rules! impl_known_layout {
365438
// `T` is sized so it has no trailing slice.
366439
const TRAILING_SLICE_ELEM_SIZE: Option<usize> = None;
367440

441+
// SAFETY:
442+
// - `MaybeUninit` has no validity requirements, so it is sound to
443+
// write any byte value, including an uninitialized byte, at any
444+
// offset.
445+
// - `MaybeUninit<T>` has the same layout as `T`, so they have the
446+
// same alignment requirement. For the same reason, their sizes
447+
// are equal.
448+
// - Since their sizes are equal, raw pointers to both types are
449+
// thin pointers, and thus can be converted using as casts. For
450+
// the same reason, the sizes of these pointers' referents are
451+
// always equal.
452+
// - `MaybeUninit<T>` has the same field offsets as `T`, and so it
453+
// contains `UnsafeCell`s at exactly the same byte ranges as `T`.
454+
type MaybeUninit = mem::MaybeUninit<$ty>;
455+
368456
// SAFETY: `.cast` preserves address and provenance.
369457
#[inline(always)]
370458
fn raw_from_ptr_len(bytes: NonNull<u8>, _elems: usize) -> NonNull<Self> {
371459
bytes.cast::<Self>()
372460
}
461+
462+
// SAFETY: `.cast` preserves pointer address and provenance.
463+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<Self::MaybeUninit>) -> NonNull<Self> {
464+
maybe_uninit.cast::<Self>()
465+
}
466+
467+
// SAFETY: `.cast` preserves pointer address and provenance.
468+
fn cast_to_maybe_uninit(slf: NonNull<Self>) -> NonNull<Self::MaybeUninit> {
469+
slf.cast::<Self::MaybeUninit>()
470+
}
373471
}
374472
};
375473
}
@@ -385,16 +483,85 @@ impl_known_layout!(
385483
impl_known_layout!(T => Option<T>);
386484
impl_known_layout!(T: ?Sized => PhantomData<T>);
387485
impl_known_layout!(T => Wrapping<T>);
388-
impl_known_layout!(T => MaybeUninit<T>);
486+
impl_known_layout!(T => mem::MaybeUninit<T>);
389487
impl_known_layout!(const N: usize, T => [T; N]);
390488

391489
safety_comment! {
392490
/// SAFETY:
393491
/// `str` and `ManuallyDrop<[T]>` have the same representations as `[u8]`
394-
/// and `[T]` repsectively. `str` has different bit validity than `[u8]`,
395-
/// but that doesn't affect the soundness of this impl.
492+
/// and `[T]` repsectively, including with respect to the locations of
493+
/// `UnsafeCell`s. `str` has different bit validity than `[u8]`, but that
494+
/// doesn't affect the soundness of this impl.
396495
unsafe_impl_known_layout!(#[repr([u8])] str);
397496
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] ManuallyDrop<T>);
497+
/// SAFETY:
498+
/// `Cell<T>` and `UnsafeCell<T>` have the same representations, including
499+
/// (trivially) with respect to the locations of `UnsafeCell`s.
500+
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(cell::UnsafeCell<T>)] cell::Cell<T>);
501+
}
502+
503+
impl<T: ?Sized + sealed::KnownLayoutSealed> sealed::KnownLayoutSealed for cell::UnsafeCell<T> {}
504+
// SAFETY: See inline comments.
505+
unsafe impl<T: ?Sized + KnownLayout> KnownLayout for cell::UnsafeCell<T> {
506+
// SAFETY: `UnsafeCell<T>` and `T` have the same size, alignment, and
507+
// trailing element size.
508+
const FIXED_PREFIX_SIZE: usize = <T as KnownLayout>::FIXED_PREFIX_SIZE;
509+
const ALIGN: NonZeroUsize = <T as KnownLayout>::ALIGN;
510+
const TRAILING_SLICE_ELEM_SIZE: Option<usize> = <T as KnownLayout>::TRAILING_SLICE_ELEM_SIZE;
511+
512+
// SAFETY:
513+
// - By `MaybeUninit` invariant, it is sound to write any byte - including
514+
// an uninitialized byte - at any byte offset in
515+
// `UnsafeCell<T::MaybeUninit>`.
516+
// - `UnsafeCell<T>` and `T` have the same size, alignment, and trailing
517+
// element size. Also, by `MaybeUninit` invariants:
518+
// - `T` and `T::MaybeUninit` have the same alignment.
519+
// - It is valid to cast `*const T` to `*const T::MaybeUninit` and
520+
// vice-versa (and likewise for `*mut`), and these operations preserve
521+
// pointer referent size.
522+
//
523+
// Thus, these properties hold between `UnsafeCell<T>` and
524+
// `UnsafeCell<T::MaybeUninit>`.
525+
// - `UnsafeCell<T>` and `UnsafeCell<T::MaybeUninit>` trivially have
526+
// `UnsafeCell`s in exactly the same locations.
527+
type MaybeUninit = cell::UnsafeCell<<T as KnownLayout>::MaybeUninit>;
528+
529+
// SAFETY: All operations preserve address and provenance. Caller
530+
// has promised that the `as` cast preserves size.
531+
#[inline(always)]
532+
fn raw_from_ptr_len(bytes: NonNull<u8>, elems: usize) -> NonNull<Self> {
533+
let slf = T::raw_from_ptr_len(bytes, elems).as_ptr();
534+
#[allow(clippy::as_conversions)]
535+
let slf = slf as *mut cell::UnsafeCell<T>;
536+
// SAFETY: `.as_ptr()` called on a non-null pointer.
537+
unsafe { NonNull::new_unchecked(slf) }
538+
}
539+
540+
// SAFETY: All operations preserve pointer address and provenance.
541+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<Self::MaybeUninit>) -> NonNull<Self> {
542+
#[allow(clippy::as_conversions)]
543+
let maybe_uninit = maybe_uninit.as_ptr() as *mut <T as KnownLayout>::MaybeUninit;
544+
// SAFETY: `.as_ptr()` called on a non-null pointer.
545+
let maybe_uninit = unsafe { NonNull::new_unchecked(maybe_uninit) };
546+
let repr = <T as KnownLayout>::cast_from_maybe_uninit(maybe_uninit).as_ptr();
547+
#[allow(clippy::as_conversions)]
548+
let slf = repr as *mut Self;
549+
// SAFETY: `.as_ptr()` called on non-null pointer.
550+
unsafe { NonNull::new_unchecked(slf) }
551+
}
552+
553+
// SAFETY: `.cast` preserves pointer address and provenance.
554+
fn cast_to_maybe_uninit(slf: NonNull<Self>) -> NonNull<Self::MaybeUninit> {
555+
#[allow(clippy::as_conversions)]
556+
let repr = slf.as_ptr() as *mut T;
557+
// SAFETY: `.as_ptr()` called on non-null pointer.
558+
let repr = unsafe { NonNull::new_unchecked(repr) };
559+
let maybe_uninit = <T as KnownLayout>::cast_to_maybe_uninit(repr).as_ptr();
560+
#[allow(clippy::as_conversions)]
561+
let maybe_uninit = maybe_uninit as *mut cell::UnsafeCell<T::MaybeUninit>;
562+
// SAFETY: `.as_ptr()` called on non-null pointer.
563+
unsafe { NonNull::new_unchecked(maybe_uninit) }
564+
}
398565
}
399566

400567
/// Types for which a sequence of bytes all set to zero represents a valid
@@ -1201,17 +1368,16 @@ safety_comment! {
12011368
/// - `Unaligned`: `MaybeUninit<T>` is guaranteed by its documentation [1]
12021369
/// to have the same alignment as `T`.
12031370
///
1204-
/// [1]
1205-
/// https://doc.rust-lang.org/nightly/core/mem/union.MaybeUninit.html#layout-1
1371+
/// [1] https://doc.rust-lang.org/nightly/core/mem/union.MaybeUninit.html#layout-1
12061372
///
12071373
/// TODO(https://github.com/google/zerocopy/issues/251): If we split
12081374
/// `FromBytes` and `RefFromBytes`, or if we introduce a separate
12091375
/// `NoCell`/`Freeze` trait, we can relax the trait bounds for `FromZeroes`
12101376
/// and `FromBytes`.
1211-
unsafe_impl!(T: FromZeroes => FromZeroes for MaybeUninit<T>);
1212-
unsafe_impl!(T: FromBytes => FromBytes for MaybeUninit<T>);
1213-
unsafe_impl!(T: Unaligned => Unaligned for MaybeUninit<T>);
1214-
assert_unaligned!(MaybeUninit<()>, MaybeUninit<u8>);
1377+
unsafe_impl!(T: FromZeroes => FromZeroes for mem::MaybeUninit<T>);
1378+
unsafe_impl!(T: FromBytes => FromBytes for mem::MaybeUninit<T>);
1379+
unsafe_impl!(T: Unaligned => Unaligned for mem::MaybeUninit<T>);
1380+
assert_unaligned!(mem::MaybeUninit<()>, mem::MaybeUninit<u8>);
12151381
}
12161382
safety_comment! {
12171383
/// SAFETY:
@@ -3716,8 +3882,11 @@ mod tests {
37163882
assert_impls!(Option<NonZeroUsize>: FromZeroes, FromBytes, AsBytes, !Unaligned);
37173883
assert_impls!(Option<NonZeroIsize>: FromZeroes, FromBytes, AsBytes, !Unaligned);
37183884

3719-
// Implements none of the ZC traits.
3885+
// Implements none of the ZC traits, but implements `KnownLayout` so
3886+
// that types like `MaybeUninit<KnownLayout>` can at least be written
3887+
// down without causing errors so that we can test them.
37203888
struct NotZerocopy;
3889+
impl_known_layout!(NotZerocopy);
37213890

37223891
assert_impls!(PhantomData<NotZerocopy>: FromZeroes, FromBytes, AsBytes, Unaligned);
37233892
assert_impls!(PhantomData<[u8]>: FromZeroes, FromBytes, AsBytes, Unaligned);
@@ -3727,8 +3896,15 @@ mod tests {
37273896
assert_impls!(ManuallyDrop<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
37283897
assert_impls!(ManuallyDrop<[NotZerocopy]>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
37293898

3899+
assert_impls!(mem::MaybeUninit<u8>: FromZeroes, FromBytes, Unaligned, !AsBytes);
3900+
assert_impls!(mem::MaybeUninit<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
3901+
37303902
assert_impls!(MaybeUninit<u8>: FromZeroes, FromBytes, Unaligned, !AsBytes);
3903+
assert_impls!(MaybeUninit<MaybeUninit<u8>>: FromZeroes, FromBytes, Unaligned, !AsBytes);
3904+
assert_impls!(MaybeUninit<[u8]>: FromZeroes, FromBytes, Unaligned, !AsBytes);
3905+
assert_impls!(MaybeUninit<MaybeUninit<[u8]>>: FromZeroes, FromBytes, Unaligned, !AsBytes);
37313906
assert_impls!(MaybeUninit<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
3907+
assert_impls!(MaybeUninit<MaybeUninit<NotZerocopy>>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
37323908

37333909
assert_impls!(Wrapping<u8>: FromZeroes, FromBytes, AsBytes, Unaligned);
37343910
assert_impls!(Wrapping<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);

src/macros.rs

+38-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/// The macro invocations are emitted, each decorated with the following
2020
/// attribute: `#[allow(clippy::undocumented_unsafe_blocks)]`.
2121
macro_rules! safety_comment {
22-
(#[doc = r" SAFETY:"] $(#[doc = $_doc:literal])* $($macro:ident!$args:tt;)*) => {
22+
(#[doc = r" SAFETY:"] $(#[doc = $_doc:literal])* $($macro:ident!$args:tt; $(#[doc = r" SAFETY:"] $(#[doc = $__doc:literal])*)?)*) => {
2323
#[allow(clippy::undocumented_unsafe_blocks)]
2424
const _: () = { $($macro!$args;)* };
2525
}
@@ -199,6 +199,7 @@ macro_rules! impl_or_verify {
199199
/// - Fixed prefix size
200200
/// - Alignment
201201
/// - (For DSTs) trailing slice element size
202+
/// - `UnsafeCell`s covering exactly the same byte ranges
202203
/// - It must be valid to perform an `as` cast from `*mut $repr` to `*mut $ty`,
203204
/// and this operation must preserve referent size (ie, `size_of_val_raw`).
204205
macro_rules! unsafe_impl_known_layout {
@@ -211,14 +212,47 @@ macro_rules! unsafe_impl_known_layout {
211212
const ALIGN: NonZeroUsize = <$repr as KnownLayout>::ALIGN;
212213
const TRAILING_SLICE_ELEM_SIZE: Option<usize> = <$repr as KnownLayout>::TRAILING_SLICE_ELEM_SIZE;
213214

215+
// SAFETY:
216+
// - By `MaybeUninit` invariant, it is sound to write any byte -
217+
// including an uninitialized byte - at any byte offset in
218+
// `$repr::MaybeUninit`.
219+
// - Caller has promised that `$ty` and `$repr` have the same
220+
// alignment, size, trailing element size, and `UnsafeCell`
221+
// locations. Also, by `MaybeUninit` invariants:
222+
// - `$repr` and `$repr::MaybeUninit` have the same alignment.
223+
// - It is valid to cast `*const $repr` to `*const
224+
// $repr::MaybeUninit` and vice-versa (and likewise for `*mut`),
225+
// and these operations preserve pointer referent size.
226+
// - `$repr` and `$repr::MaybeUninit` contain `UnsafeCell`s at
227+
// exactly the same byte ranges.
228+
//
229+
// Thus, all of the same properties hold between `$ty` and
230+
// `$repr::MaybeUninit`.
231+
type MaybeUninit = <$repr as KnownLayout>::MaybeUninit;
232+
214233
// SAFETY: All operations preserve address and provenance. Caller
215234
// has promised that the `as` cast preserves size.
216235
#[inline(always)]
217236
fn raw_from_ptr_len(bytes: NonNull<u8>, elems: usize) -> NonNull<Self> {
237+
Self::cast_from_maybe_uninit(Self::MaybeUninit::raw_from_ptr_len(bytes, elems))
238+
}
239+
240+
// SAFETY: All operations preserve pointer address and provenance.
241+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<Self::MaybeUninit>) -> NonNull<Self> {
242+
let repr = <$repr as KnownLayout>::cast_from_maybe_uninit(maybe_uninit).as_ptr();
243+
#[allow(clippy::as_conversions)]
244+
let slf = repr as *mut Self;
245+
// SAFETY: `.as_ptr()` called on non-null pointer.
246+
unsafe { NonNull::new_unchecked(slf) }
247+
}
248+
249+
// SAFETY: `.cast` preserves pointer address and provenance.
250+
fn cast_to_maybe_uninit(slf: NonNull<Self>) -> NonNull<Self::MaybeUninit> {
218251
#[allow(clippy::as_conversions)]
219-
let ptr = <$repr>::raw_from_ptr_len(bytes, elems).as_ptr() as *mut Self;
220-
// SAFETY: `ptr` was converted from `bytes`, which is non-null.
221-
unsafe { NonNull::new_unchecked(ptr) }
252+
let repr = slf.as_ptr() as *mut $repr;
253+
// SAFETY: `.as_ptr()` called on non-null pointer.
254+
let repr = unsafe { NonNull::new_unchecked(repr) };
255+
<$repr as KnownLayout>::cast_to_maybe_uninit(repr)
222256
}
223257
}
224258
};

0 commit comments

Comments
 (0)