From 80ba6dd78b09fbc22ff5922109464bd659aedc2c Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sun, 27 Mar 2022 15:09:18 -0700 Subject: [PATCH] Add `mem::conjure_zst` for creating ZSTs out of nothing --- library/alloc/src/lib.rs | 1 + library/alloc/src/vec/into_iter.rs | 4 +- library/core/src/array/mod.rs | 5 +- library/core/src/mem/mod.rs | 50 +++++++++++++++++++ .../ui/consts/std/conjure_uninhabited_zst.rs | 11 ++++ .../consts/std/conjure_uninhabited_zst.stderr | 14 ++++++ 6 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 src/test/ui/consts/std/conjure_uninhabited_zst.rs create mode 100644 src/test/ui/consts/std/conjure_uninhabited_zst.stderr diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 0a180b83355e0..2225fb86394e4 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -116,6 +116,7 @@ #![feature(iter_advance_by)] #![feature(layout_for_ptr)] #![feature(maybe_uninit_slice)] +#![feature(mem_conjure_zst)] #![cfg_attr(test, feature(new_uninit))] #![feature(nonnull_slice_from_raw_parts)] #![feature(pattern)] diff --git a/library/alloc/src/vec/into_iter.rs b/library/alloc/src/vec/into_iter.rs index f17b8d71b3a1a..1b3c229cc6679 100644 --- a/library/alloc/src/vec/into_iter.rs +++ b/library/alloc/src/vec/into_iter.rs @@ -147,7 +147,7 @@ impl Iterator for IntoIter { self.ptr = unsafe { arith_offset(self.ptr as *const i8, 1) as *mut T }; // Make up a value of this ZST. - Some(unsafe { mem::zeroed() }) + Some(unsafe { mem::conjure_zst() }) } else { let old = self.ptr; self.ptr = unsafe { self.ptr.offset(1) }; @@ -224,7 +224,7 @@ impl DoubleEndedIterator for IntoIter { self.end = unsafe { arith_offset(self.end as *const i8, -1) as *mut T }; // Make up a value of this ZST. - Some(unsafe { mem::zeroed() }) + Some(unsafe { mem::conjure_zst() }) } else { self.end = unsafe { self.end.offset(-1) }; diff --git a/library/core/src/array/mod.rs b/library/core/src/array/mod.rs index 20dfbc6347c4f..4f288dcf7cf5e 100644 --- a/library/core/src/array/mod.rs +++ b/library/core/src/array/mod.rs @@ -799,8 +799,9 @@ where R: Residual<[T; N]>, { if N == 0 { - // SAFETY: An empty array is always inhabited and has no validity invariants. - return unsafe { Some(Try::from_output(mem::zeroed())) }; + // SAFETY: An empty array is always inhabited and zero-sized, + // regardless of the size or inhabitedness of its element type. + return unsafe { Some(Try::from_output(mem::conjure_zst())) }; } struct Guard<'a, T, const N: usize> { diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 8a99bed6a96ab..cf5a70554fdfa 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -678,6 +678,56 @@ pub unsafe fn uninitialized() -> T { } } +/// Create a fresh instance of the inhabited ZST type `T`. +/// +/// Prefer this to [`zeroed`] or [`uninitialized`] or [`transmute_copy`] +/// in places where you know that `T` is zero-sized, but don't have a bound +/// (such as [`Default`]) that would allow you to instantiate it using safe code. +/// +/// If you're not sure whether `T` is an inhabited ZST, then you should be +/// using [`MaybeUninit`], not this function. +/// +/// # Safety +/// +/// - `size_of::()` must be zero. +/// +/// - `T` must be *inhabited*. (It must not be a zero-variant `enum`, for example.) +/// +/// - You must use the value only in ways which do not violate any *safety* +/// invariants of the type. +/// +/// While it's easy to create a *valid* instance of an inhabited ZST, since having +/// no bits in its representation means there's only one possible value, that +/// doesn't mean that it's always *sound* to do so. +/// +/// For example, a library with a global semaphore could give out ZST tokens +/// on `acquire`, and by them being `!Default`+`!Clone` could consume them +/// in `release` to ensure that it's called at most once per `acquire`. +/// Or a library could use a `!Default`+`!Send` token to ensure it's used only +/// from the thread on which it was initialized. +/// +/// # Examples +/// +/// ``` +/// #![feature(mem_conjure_zst)] +/// use std::mem::conjure_zst; +/// +/// assert_eq!(unsafe { conjure_zst::<()>() }, ()); +/// assert_eq!(unsafe { conjure_zst::<[i32; 0]>() }, []); +/// ``` +#[inline(always)] +#[must_use] +#[unstable(feature = "mem_conjure_zst", issue = "95383")] +#[track_caller] +pub const unsafe fn conjure_zst() -> T { + assert!(size_of::() == 0); // FIXME: Use assert_eq! once that's allowed in const + + // SAFETY: because the caller must guarantee that it's inhabited and zero-sized, + // there's nothing in the representation that needs to be set. + // `assume_init` calls `assert_inhabited`, so we don't need to here. + unsafe { MaybeUninit::uninit().assume_init() } +} + /// Swaps the values at two mutable locations, without deinitializing either one. /// /// * If you want to swap with a default or dummy value, see [`take`]. diff --git a/src/test/ui/consts/std/conjure_uninhabited_zst.rs b/src/test/ui/consts/std/conjure_uninhabited_zst.rs new file mode 100644 index 0000000000000..8eb4c0582e53b --- /dev/null +++ b/src/test/ui/consts/std/conjure_uninhabited_zst.rs @@ -0,0 +1,11 @@ +#![feature(mem_conjure_zst)] + +use std::convert::Infallible; +use std::mem::conjure_zst; + +// not ok, since the type needs to be inhabited +const CONJURE_INVALID: Infallible = unsafe { conjure_zst() }; +//~^ ERROR any use of this value will cause an error +//~^^ WARN will become a hard error in a future release + +fn main() {} diff --git a/src/test/ui/consts/std/conjure_uninhabited_zst.stderr b/src/test/ui/consts/std/conjure_uninhabited_zst.stderr new file mode 100644 index 0000000000000..991dc6e3a0dd1 --- /dev/null +++ b/src/test/ui/consts/std/conjure_uninhabited_zst.stderr @@ -0,0 +1,14 @@ +error: any use of this value will cause an error + --> $DIR/conjure_uninhabited_zst.rs:7:46 + | +LL | const CONJURE_INVALID: Infallible = unsafe { conjure_zst() }; + | ---------------------------------------------^^^^^^^^^^^^^--- + | | + | aborted execution: attempted to instantiate uninhabited type `Infallible` + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + +error: aborting due to previous error +