Skip to content

Commit 3113e26

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 b0735fc commit 3113e26

File tree

3 files changed

+482
-15
lines changed

3 files changed

+482
-15
lines changed

src/lib.rs

Lines changed: 187 additions & 11 deletions
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,11 +229,54 @@ 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
/// SAFETY: The returned pointer has the same address and provenance as
233258
/// `bytes`. If `Self` is a DST, the returned pointer's referent has `elems`
234259
/// elements in its trailing slice.
235260
#[doc(hidden)]
236261
fn raw_from_ptr_len(bytes: NonNull<u8>, elems: usize) -> NonNull<Self>;
262+
263+
/// Converts a pointer at the type level.
264+
///
265+
/// # Safety
266+
///
267+
/// Callers may assume that the memory region addressed by the return value
268+
/// is the same as that addressed by the argument, and that both the return
269+
/// value and the argument have the same provenance.
270+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<Self::MaybeUninit>) -> NonNull<Self>;
271+
272+
/// Converts a pointer at the type level.
273+
///
274+
/// # Safety
275+
///
276+
/// Callers may assume that the memory region addressed by the return value
277+
/// is the same as that addressed by the argument, and that both the return
278+
/// value and the argument have the same provenance.
279+
fn cast_to_maybe_uninit(slf: NonNull<Self>) -> NonNull<Self::MaybeUninit>;
237280
}
238281

239282
impl<T: KnownLayout> sealed::KnownLayoutSealed for [T] {}
@@ -253,6 +296,22 @@ unsafe impl<T: KnownLayout> KnownLayout for [T] {
253296
};
254297
const TRAILING_SLICE_ELEM_SIZE: Option<usize> = Some(mem::size_of::<T>());
255298

299+
// SAFETY:
300+
// - `MaybeUninit` has no bit validity requirements and `[U]` has the same
301+
// bit validity requirements as `U`, so `[MaybeUninit<T>]` has no bit
302+
// validity requirements. Thus, it is sound to write any byte value,
303+
// including an uninitialized byte, at any byte offset.
304+
// - Since `MaybeUninit<T>` has the same layout as `T`, and `[U]` has the
305+
// same alignment as `U`, `[MaybeUninit<T>]` has the same alignment as
306+
// `[T]`.
307+
// - `[T]` and `[MaybeUninit<T>]` are both slice types, and so pointers can
308+
// be converted using an `as` cast. Since `T` and `MaybeUninit<T>` have
309+
// the same size, and since such a cast preserves the number of elements
310+
// in the slice, the referent slices themselves will have the same size.
311+
// - `MaybeUninit<T>` has the same field offsets as `[T]`, and so it
312+
// contains `UnsafeCell`s at exactly the same byte ranges as `[T]`.
313+
type MaybeUninit = [mem::MaybeUninit<T>];
314+
256315
// SAFETY: `.cast` preserves address and provenance. The returned pointer
257316
// refers to an object with `elems` elements by construction.
258317
#[inline(always)]
@@ -261,6 +320,20 @@ unsafe impl<T: KnownLayout> KnownLayout for [T] {
261320
#[allow(unstable_name_collisions)]
262321
NonNull::slice_from_raw_parts(data.cast::<T>(), elems)
263322
}
323+
324+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<[mem::MaybeUninit<T>]>) -> NonNull<[T]> {
325+
let (ptr, len) = (maybe_uninit.cast::<T>(), maybe_uninit.len());
326+
// TODO(#67): Remove this allow. See NonNullExt for more details.
327+
#[allow(unstable_name_collisions)]
328+
NonNull::slice_from_raw_parts(ptr, len)
329+
}
330+
331+
fn cast_to_maybe_uninit(slf: NonNull<[T]>) -> NonNull<[mem::MaybeUninit<T>]> {
332+
let (ptr, len) = (slf.cast::<mem::MaybeUninit<T>>(), slf.len());
333+
// TODO(#67): Remove this allow. See NonNullExt for more details.
334+
#[allow(unstable_name_collisions)]
335+
NonNull::slice_from_raw_parts(ptr, len)
336+
}
264337
}
265338

266339
/// Implements `KnownLayout` for a sized type.
@@ -297,11 +370,36 @@ macro_rules! impl_known_layout {
297370
// `T` is sized so it has no trailing slice.
298371
const TRAILING_SLICE_ELEM_SIZE: Option<usize> = None;
299372

373+
// SAFETY:
374+
// - `MaybeUninit` has no validity requirements, so it is sound to
375+
// write any byte value, including an uninitialized byte, at any
376+
// offset.
377+
// - `MaybeUninit<T>` has the same layout as `T`, so they have the
378+
// same alignment requirement. For the same reason, their sizes
379+
// are equal.
380+
// - Since their sizes are equal, raw pointers to both types are
381+
// thin pointers, and thus can be converted using as casts. For
382+
// the same reason, the sizes of these pointers' referents are
383+
// always equal.
384+
// - `MaybeUninit<T>` has the same field offsets as `T`, and so it
385+
// contains `UnsafeCell`s at exactly the same byte ranges as `T`.
386+
type MaybeUninit = mem::MaybeUninit<$ty>;
387+
300388
// SAFETY: `.cast` preserves address and provenance.
301389
#[inline(always)]
302390
fn raw_from_ptr_len(bytes: NonNull<u8>, _elems: usize) -> NonNull<Self> {
303391
bytes.cast::<Self>()
304392
}
393+
394+
// SAFETY: `.cast` preserves pointer address and provenance.
395+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<Self::MaybeUninit>) -> NonNull<Self> {
396+
maybe_uninit.cast::<Self>()
397+
}
398+
399+
// SAFETY: `.cast` preserves pointer address and provenance.
400+
fn cast_to_maybe_uninit(slf: NonNull<Self>) -> NonNull<Self::MaybeUninit> {
401+
slf.cast::<Self::MaybeUninit>()
402+
}
305403
}
306404
};
307405
}
@@ -317,16 +415,85 @@ impl_known_layout!(
317415
impl_known_layout!(T => Option<T>);
318416
impl_known_layout!(T: ?Sized => PhantomData<T>);
319417
impl_known_layout!(T => Wrapping<T>);
320-
impl_known_layout!(T => MaybeUninit<T>);
418+
impl_known_layout!(T => mem::MaybeUninit<T>);
321419
impl_known_layout!(const N: usize, T => [T; N]);
322420

323421
safety_comment! {
324422
/// SAFETY:
325423
/// `str` and `ManuallyDrop<[T]>` have the same representations as `[u8]`
326-
/// and `[T]` repsectively. `str` has different bit validity than `[u8]`,
327-
/// but that doesn't affect the soundness of this impl.
424+
/// and `[T]` repsectively, including with respect to the locations of
425+
/// `UnsafeCell`s. `str` has different bit validity than `[u8]`, but that
426+
/// doesn't affect the soundness of this impl.
328427
unsafe_impl_known_layout!(#[repr([u8])] str);
329428
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] ManuallyDrop<T>);
429+
/// SAFETY:
430+
/// `Cell<T>` and `UnsafeCell<T>` have the same representations, including
431+
/// (trivially) with respect to the locations of `UnsafeCell`s.
432+
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(cell::UnsafeCell<T>)] cell::Cell<T>);
433+
}
434+
435+
impl<T: ?Sized + sealed::KnownLayoutSealed> sealed::KnownLayoutSealed for cell::UnsafeCell<T> {}
436+
// SAFETY: See inline comments.
437+
unsafe impl<T: ?Sized + KnownLayout> KnownLayout for cell::UnsafeCell<T> {
438+
// SAFETY: `UnsafeCell<T>` and `T` have the same size, alignment, and
439+
// trailing element size.
440+
const FIXED_PREFIX_SIZE: usize = <T as KnownLayout>::FIXED_PREFIX_SIZE;
441+
const ALIGN: NonZeroUsize = <T as KnownLayout>::ALIGN;
442+
const TRAILING_SLICE_ELEM_SIZE: Option<usize> = <T as KnownLayout>::TRAILING_SLICE_ELEM_SIZE;
443+
444+
// SAFETY:
445+
// - By `MaybeUninit` invariant, it is sound to write any byte - including
446+
// an uninitialized byte - at any byte offset in
447+
// `UnsafeCell<T::MaybeUninit>`.
448+
// - `UnsafeCell<T>` and `T` have the same size, alignment, and trailing
449+
// element size. Also, by `MaybeUninit` invariants:
450+
// - `T` and `T::MaybeUninit` have the same alignment.
451+
// - It is valid to cast `*const T` to `*const T::MaybeUninit` and
452+
// vice-versa (and likewise for `*mut`), and these operations preserve
453+
// pointer referent size.
454+
//
455+
// Thus, these properties hold between `UnsafeCell<T>` and
456+
// `UnsafeCell<T::MaybeUninit>`.
457+
// - `UnsafeCell<T>` and `UnsafeCell<T::MaybeUninit>` trivially have
458+
// `UnsafeCell`s in exactly the same locations.
459+
type MaybeUninit = cell::UnsafeCell<<T as KnownLayout>::MaybeUninit>;
460+
461+
// SAFETY: All operations preserve address and provenance. Caller
462+
// has promised that the `as` cast preserves size.
463+
#[inline(always)]
464+
fn raw_from_ptr_len(bytes: NonNull<u8>, elems: usize) -> NonNull<Self> {
465+
let slf = T::raw_from_ptr_len(bytes, elems).as_ptr();
466+
#[allow(clippy::as_conversions)]
467+
let slf = slf as *mut cell::UnsafeCell<T>;
468+
// SAFETY: `.as_ptr()` called on a non-null pointer.
469+
unsafe { NonNull::new_unchecked(slf) }
470+
}
471+
472+
// SAFETY: All operations preserve pointer address and provenance.
473+
fn cast_from_maybe_uninit(maybe_uninit: NonNull<Self::MaybeUninit>) -> NonNull<Self> {
474+
#[allow(clippy::as_conversions)]
475+
let maybe_uninit = maybe_uninit.as_ptr() as *mut <T as KnownLayout>::MaybeUninit;
476+
// SAFETY: `.as_ptr()` called on a non-null pointer.
477+
let maybe_uninit = unsafe { NonNull::new_unchecked(maybe_uninit) };
478+
let repr = <T as KnownLayout>::cast_from_maybe_uninit(maybe_uninit).as_ptr();
479+
#[allow(clippy::as_conversions)]
480+
let slf = repr as *mut Self;
481+
// SAFETY: `.as_ptr()` called on non-null pointer.
482+
unsafe { NonNull::new_unchecked(slf) }
483+
}
484+
485+
// SAFETY: `.cast` preserves pointer address and provenance.
486+
fn cast_to_maybe_uninit(slf: NonNull<Self>) -> NonNull<Self::MaybeUninit> {
487+
#[allow(clippy::as_conversions)]
488+
let repr = slf.as_ptr() as *mut T;
489+
// SAFETY: `.as_ptr()` called on non-null pointer.
490+
let repr = unsafe { NonNull::new_unchecked(repr) };
491+
let maybe_uninit = <T as KnownLayout>::cast_to_maybe_uninit(repr).as_ptr();
492+
#[allow(clippy::as_conversions)]
493+
let maybe_uninit = maybe_uninit as *mut cell::UnsafeCell<T::MaybeUninit>;
494+
// SAFETY: `.as_ptr()` called on non-null pointer.
495+
unsafe { NonNull::new_unchecked(maybe_uninit) }
496+
}
330497
}
331498

332499
/// Types for which a sequence of bytes all set to zero represents a valid
@@ -1133,17 +1300,16 @@ safety_comment! {
11331300
/// - `Unaligned`: `MaybeUninit<T>` is guaranteed by its documentation [1]
11341301
/// to have the same alignment as `T`.
11351302
///
1136-
/// [1]
1137-
/// https://doc.rust-lang.org/nightly/core/mem/union.MaybeUninit.html#layout-1
1303+
/// [1] https://doc.rust-lang.org/nightly/core/mem/union.MaybeUninit.html#layout-1
11381304
///
11391305
/// TODO(https://github.com/google/zerocopy/issues/251): If we split
11401306
/// `FromBytes` and `RefFromBytes`, or if we introduce a separate
11411307
/// `NoCell`/`Freeze` trait, we can relax the trait bounds for `FromZeroes`
11421308
/// and `FromBytes`.
1143-
unsafe_impl!(T: FromZeroes => FromZeroes for MaybeUninit<T>);
1144-
unsafe_impl!(T: FromBytes => FromBytes for MaybeUninit<T>);
1145-
unsafe_impl!(T: Unaligned => Unaligned for MaybeUninit<T>);
1146-
assert_unaligned!(MaybeUninit<()>, MaybeUninit<u8>);
1309+
unsafe_impl!(T: FromZeroes => FromZeroes for mem::MaybeUninit<T>);
1310+
unsafe_impl!(T: FromBytes => FromBytes for mem::MaybeUninit<T>);
1311+
unsafe_impl!(T: Unaligned => Unaligned for mem::MaybeUninit<T>);
1312+
assert_unaligned!(mem::MaybeUninit<()>, mem::MaybeUninit<u8>);
11471313
}
11481314
safety_comment! {
11491315
/// SAFETY:
@@ -3648,8 +3814,11 @@ mod tests {
36483814
assert_impls!(Option<NonZeroUsize>: FromZeroes, FromBytes, AsBytes, !Unaligned);
36493815
assert_impls!(Option<NonZeroIsize>: FromZeroes, FromBytes, AsBytes, !Unaligned);
36503816

3651-
// Implements none of the ZC traits.
3817+
// Implements none of the ZC traits, but implements `KnownLayout` so
3818+
// that types like `MaybeUninit<KnownLayout>` can at least be written
3819+
// down without causing errors so that we can test them.
36523820
struct NotZerocopy;
3821+
impl_known_layout!(NotZerocopy);
36533822

36543823
assert_impls!(PhantomData<NotZerocopy>: FromZeroes, FromBytes, AsBytes, Unaligned);
36553824
assert_impls!(PhantomData<[u8]>: FromZeroes, FromBytes, AsBytes, Unaligned);
@@ -3659,8 +3828,15 @@ mod tests {
36593828
assert_impls!(ManuallyDrop<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
36603829
assert_impls!(ManuallyDrop<[NotZerocopy]>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
36613830

3831+
assert_impls!(mem::MaybeUninit<u8>: FromZeroes, FromBytes, Unaligned, !AsBytes);
3832+
assert_impls!(mem::MaybeUninit<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
3833+
36623834
assert_impls!(MaybeUninit<u8>: FromZeroes, FromBytes, Unaligned, !AsBytes);
3835+
assert_impls!(MaybeUninit<MaybeUninit<u8>>: FromZeroes, FromBytes, Unaligned, !AsBytes);
3836+
assert_impls!(MaybeUninit<[u8]>: FromZeroes, FromBytes, Unaligned, !AsBytes);
3837+
assert_impls!(MaybeUninit<MaybeUninit<[u8]>>: FromZeroes, FromBytes, Unaligned, !AsBytes);
36633838
assert_impls!(MaybeUninit<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
3839+
assert_impls!(MaybeUninit<MaybeUninit<NotZerocopy>>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
36643840

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

src/macros.rs

Lines changed: 38 additions & 4 deletions
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)