Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mem::conjure_zst for creating ZSTs out of nothing #95385

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Expand Up @@ -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)]
Expand Down
4 changes: 2 additions & 2 deletions library/alloc/src/vec/into_iter.rs
Expand Up @@ -147,7 +147,7 @@ impl<T, A: Allocator> Iterator for IntoIter<T, A> {
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) };
Expand Down Expand Up @@ -224,7 +224,7 @@ impl<T, A: Allocator> DoubleEndedIterator for IntoIter<T, A> {
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) };

Expand Down
5 changes: 3 additions & 2 deletions library/core/src/array/mod.rs
Expand Up @@ -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> {
Expand Down
52 changes: 52 additions & 0 deletions library/core/src/mem/mod.rs
Expand Up @@ -678,6 +678,58 @@ pub unsafe fn uninitialized<T>() -> T {
}
}

/// Create a fresh instance of the inhabited zero-sized 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::<T>()` 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>() -> T {
// This is not a guarantee exposed to clients, but it'll easily optimize out
// in the sound cases, so we might as well check because we can.
assert!(size_of::<T>() == 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`].
Expand Down
11 changes: 11 additions & 0 deletions 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() {}
14 changes: 14 additions & 0 deletions 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 <https://github.com/rust-lang/rust/issues/71800>

error: aborting due to previous error