diff --git a/library/core/src/array/drain.rs b/library/core/src/array/drain.rs index 5fadf907b6219..1c6137191324c 100644 --- a/library/core/src/array/drain.rs +++ b/library/core/src/array/drain.rs @@ -1,76 +1,108 @@ -use crate::iter::{TrustedLen, UncheckedIterator}; -use crate::mem::ManuallyDrop; -use crate::ptr::drop_in_place; -use crate::slice; +use crate::marker::{Destruct, PhantomData}; +use crate::mem::{ManuallyDrop, SizedTypeProperties, conjure_zst}; +use crate::ptr::{NonNull, drop_in_place, from_raw_parts_mut, null_mut}; -/// A situationally-optimized version of `array.into_iter().for_each(func)`. -/// -/// [`crate::array::IntoIter`]s are great when you need an owned iterator, but -/// storing the entire array *inside* the iterator like that can sometimes -/// pessimize code. Notable, it can be more bytes than you really want to move -/// around, and because the array accesses index into it SRoA has a harder time -/// optimizing away the type than it does iterators that just hold a couple pointers. -/// -/// Thus this function exists, which gives a way to get *moved* access to the -/// elements of an array using a small iterator -- no bigger than a slice iterator. -/// -/// The function-taking-a-closure structure makes it safe, as it keeps callers -/// from looking at already-dropped elements. -pub(crate) fn drain_array_with( - array: [T; N], - func: impl for<'a> FnOnce(Drain<'a, T>) -> R, -) -> R { - let mut array = ManuallyDrop::new(array); - // SAFETY: Now that the local won't drop it, it's ok to construct the `Drain` which will. - let drain = Drain(array.iter_mut()); - func(drain) +impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> { + /// This function returns a function that lets you index the given array in const. + /// As implemented it can optimize better than iterators, and can be constified. + /// It acts like a sort of guard (owns the array) and iterator combined, which can be implemented + /// as it is a struct that implements const fn; + /// in that regard it is somewhat similar to an array::Iter implementing `UncheckedIterator`. + /// The only method you're really allowed to call is `next()`, + /// anything else is more or less UB, hence this function being unsafe. + /// Moved elements will not be dropped. + /// This will also not actually store the array. + /// + /// SAFETY: must only be called `N` times. Thou shalt not drop the array either. + // FIXME(const-hack): this is a hack for `let guard = Guard(array); |i| f(guard[i])`. + #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] + pub(super) const unsafe fn new(array: &'l mut ManuallyDrop<[T; N]>, f: &'f mut F) -> Self { + // dont drop the array, transfers "ownership" to Self + let ptr: NonNull = NonNull::from_mut(array).cast(); + // SAFETY: + // Adding `slice.len()` to the starting pointer gives a pointer + // at the end of `slice`. `end` will never be dereferenced, only checked + // for direct pointer equality with `ptr` to check if the drainer is done. + unsafe { + let end = if T::IS_ZST { null_mut() } else { ptr.as_ptr().add(N) }; + Self { ptr, end, f, l: PhantomData } + } + } } -/// See [`drain_array_with`] -- this is `pub(crate)` only so it's allowed to be -/// mentioned in the signature of that method. (Otherwise it hits `E0446`.) -// INVARIANT: It's ok to drop the remainder of the inner iterator. -pub(crate) struct Drain<'a, T>(slice::IterMut<'a, T>); +/// See [`Drain::new`]; this is our fake iterator. +#[rustc_const_unstable(feature = "array_try_map", issue = "79711")] +#[unstable(feature = "array_try_map", issue = "79711")] +pub(super) struct Drain<'l, 'f, T, const N: usize, F> { + // FIXME(const-hack): This is essentially a slice::IterMut<'static>, replace when possible. + /// The pointer to the next element to return, or the past-the-end location + /// if the drainer is empty. + /// + /// This address will be used for all ZST elements, never changed. + /// As we "own" this array, we dont need to store any lifetime. + ptr: NonNull, + /// For non-ZSTs, the non-null pointer to the past-the-end element. + /// For ZSTs, this is null. + end: *mut T, -impl Drop for Drain<'_, T> { - fn drop(&mut self) { - // SAFETY: By the type invariant, we're allowed to drop all these. - unsafe { drop_in_place(self.0.as_mut_slice()) } - } + f: &'f mut F, + l: PhantomData<&'l mut [T; N]>, } -impl Iterator for Drain<'_, T> { - type Item = T; - - #[inline] - fn next(&mut self) -> Option { - let p: *const T = self.0.next()?; - // SAFETY: The iterator was already advanced, so we won't drop this later. - Some(unsafe { p.read() }) - } +#[rustc_const_unstable(feature = "array_try_map", issue = "79711")] +#[unstable(feature = "array_try_map", issue = "79711")] +impl const FnOnce<(usize,)> for &mut Drain<'_, '_, T, N, F> +where + F: [const] FnMut(T) -> U, +{ + type Output = U; - #[inline] - fn size_hint(&self) -> (usize, Option) { - let n = self.len(); - (n, Some(n)) + /// This implementation is useless. + extern "rust-call" fn call_once(mut self, args: (usize,)) -> Self::Output { + self.call_mut(args) } } - -impl ExactSizeIterator for Drain<'_, T> { - #[inline] - fn len(&self) -> usize { - self.0.len() +#[rustc_const_unstable(feature = "array_try_map", issue = "79711")] +#[unstable(feature = "array_try_map", issue = "79711")] +impl const FnMut<(usize,)> for &mut Drain<'_, '_, T, N, F> +where + F: [const] FnMut(T) -> U, +{ + // FIXME(const-hack): ideally this would be an unsafe fn `next()`, and to use it you would instead `|_| unsafe { drain.next() }`. + extern "rust-call" fn call_mut( + &mut self, + (_ /* ignore argument */,): (usize,), + ) -> Self::Output { + if T::IS_ZST { + // its UB to call this more than N times, so returning more ZSTs is valid. + // SAFETY: its a ZST? we conjur. + (self.f)(unsafe { conjure_zst::() }) + } else { + // increment before moving; if `f` panics, we drop the rest. + let p = self.ptr; + // SAFETY: caller guarantees never called more than N times (see `Drain::new`) + self.ptr = unsafe { self.ptr.add(1) }; + // SAFETY: we are allowed to move this. + (self.f)(unsafe { p.read() }) + } } } +#[rustc_const_unstable(feature = "array_try_map", issue = "79711")] +#[unstable(feature = "array_try_map", issue = "79711")] +impl const Drop for Drain<'_, '_, T, N, F> { + fn drop(&mut self) { + if !T::IS_ZST { + // SAFETY: we cant read more than N elements + let slice = unsafe { + from_raw_parts_mut::<[T]>( + self.ptr.as_ptr(), + // SAFETY: `start <= end` + self.end.offset_from_unsigned(self.ptr.as_ptr()), + ) + }; -// SAFETY: This is a 1:1 wrapper for a slice iterator, which is also `TrustedLen`. -unsafe impl TrustedLen for Drain<'_, T> {} - -impl UncheckedIterator for Drain<'_, T> { - unsafe fn next_unchecked(&mut self) -> T { - // SAFETY: `Drain` is 1:1 with the inner iterator, so if the caller promised - // that there's an element left, the inner iterator has one too. - let p: *const T = unsafe { self.0.next_unchecked() }; - // SAFETY: The iterator was already advanced, so we won't drop this later. - unsafe { p.read() } + // SAFETY: By the type invariant, we're allowed to drop all these. (we own it, after all) + unsafe { drop_in_place(slice) } + } } } diff --git a/library/core/src/array/mod.rs b/library/core/src/array/mod.rs index 2dd639d68f0ea..c8d0622059cde 100644 --- a/library/core/src/array/mod.rs +++ b/library/core/src/array/mod.rs @@ -12,7 +12,8 @@ use crate::error::Error; use crate::hash::{self, Hash}; use crate::intrinsics::transmute_unchecked; use crate::iter::{UncheckedIterator, repeat_n}; -use crate::mem::{self, MaybeUninit}; +use crate::marker::Destruct; +use crate::mem::{self, ManuallyDrop, MaybeUninit}; use crate::ops::{ ChangeOutputType, ControlFlow, FromResidual, Index, IndexMut, NeverShortCircuit, Residual, Try, }; @@ -25,7 +26,6 @@ mod drain; mod equality; mod iter; -pub(crate) use drain::drain_array_with; #[stable(feature = "array_value_iter", since = "1.51.0")] pub use iter::IntoIter; @@ -105,9 +105,10 @@ pub fn repeat(val: T) -> [T; N] { /// ``` #[inline] #[stable(feature = "array_from_fn", since = "1.63.0")] -pub fn from_fn(f: F) -> [T; N] +#[rustc_const_unstable(feature = "const_array", issue = "147606")] +pub const fn from_fn(f: F) -> [T; N] where - F: FnMut(usize) -> T, + F: [const] FnMut(usize) -> T + [const] Destruct, { try_from_fn(NeverShortCircuit::wrap_mut_1(f)).0 } @@ -143,11 +144,11 @@ where /// ``` #[inline] #[unstable(feature = "array_try_from_fn", issue = "89379")] -pub fn try_from_fn(cb: F) -> ChangeOutputType +#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")] +pub const fn try_from_fn(cb: F) -> ChangeOutputType where - F: FnMut(usize) -> R, - R: Try, - R::Residual: Residual<[R::Output; N]>, + R: [const] Try, Output: [const] Destruct>, + F: [const] FnMut(usize) -> R + [const] Destruct, { let mut array = [const { MaybeUninit::uninit() }; N]; match try_from_fn_erased(&mut array, cb) { @@ -549,9 +550,12 @@ impl [T; N] { /// ``` #[must_use] #[stable(feature = "array_map", since = "1.55.0")] - pub fn map(self, f: F) -> [U; N] + #[rustc_const_unstable(feature = "const_array", issue = "147606")] + pub const fn map(self, f: F) -> [U; N] where - F: FnMut(T) -> U, + F: [const] FnMut(T) -> U + [const] Destruct, + U: [const] Destruct, + T: [const] Destruct, { self.try_map(NeverShortCircuit::wrap_mut_1(f)).0 } @@ -587,11 +591,19 @@ impl [T; N] { /// assert_eq!(c, Some(a)); /// ``` #[unstable(feature = "array_try_map", issue = "79711")] - pub fn try_map(self, f: impl FnMut(T) -> R) -> ChangeOutputType + #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] + pub const fn try_map( + self, + mut f: impl [const] FnMut(T) -> R + [const] Destruct, + ) -> ChangeOutputType where - R: Try>, + R: [const] Try, Output: [const] Destruct>, + T: [const] Destruct, { - drain_array_with(self, |iter| try_from_trusted_iterator(iter.map(f))) + let mut me = ManuallyDrop::new(self); + // SAFETY: try_from_fn calls `f` N times. + let mut f = unsafe { drain::Drain::new(&mut me, &mut f) }; + try_from_fn(&mut f) } /// Returns a slice containing the entire array. Equivalent to `&s[..]`. @@ -885,13 +897,11 @@ where /// not optimizing away. So if you give it a shot, make sure to watch what /// happens in the codegen tests. #[inline] -fn try_from_fn_erased( - buffer: &mut [MaybeUninit], - mut generator: impl FnMut(usize) -> R, -) -> ControlFlow -where - R: Try, -{ +#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")] +const fn try_from_fn_erased>( + buffer: &mut [MaybeUninit], + mut generator: impl [const] FnMut(usize) -> R + [const] Destruct, +) -> ControlFlow { let mut guard = Guard { array_mut: buffer, initialized: 0 }; while guard.initialized < guard.array_mut.len() { @@ -930,7 +940,8 @@ impl Guard<'_, T> { /// /// No more than N elements must be initialized. #[inline] - pub(crate) unsafe fn push_unchecked(&mut self, item: T) { + #[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")] + pub(crate) const unsafe fn push_unchecked(&mut self, item: T) { // SAFETY: If `initialized` was correct before and the caller does not // invoke this method more than N times then writes will be in-bounds // and slots will not be initialized more than once. @@ -941,11 +952,11 @@ impl Guard<'_, T> { } } -impl Drop for Guard<'_, T> { +#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")] +impl const Drop for Guard<'_, T> { #[inline] fn drop(&mut self) { debug_assert!(self.initialized <= self.array_mut.len()); - // SAFETY: this slice will contain only initialized objects. unsafe { self.array_mut.get_unchecked_mut(..self.initialized).assume_init_drop(); diff --git a/library/core/src/ops/try_trait.rs b/library/core/src/ops/try_trait.rs index 204291886589e..34000f6d6b218 100644 --- a/library/core/src/ops/try_trait.rs +++ b/library/core/src/ops/try_trait.rs @@ -1,3 +1,4 @@ +use crate::marker::{Destruct, PhantomData}; use crate::ops::ControlFlow; /// The `?` operator and `try {}` blocks. @@ -363,6 +364,7 @@ where pub const trait Residual: Sized { /// The "return" type of this meta-function. #[unstable(feature = "try_trait_v2_residual", issue = "91285")] + // FIXME: ought to be implied type TryType: [const] Try; } @@ -396,6 +398,25 @@ pub(crate) type ChangeOutputType>, V> = /// Not currently planned to be exposed publicly, so just `pub(crate)`. #[repr(transparent)] pub(crate) struct NeverShortCircuit(pub T); +// FIXME(const-hack): replace with `|a| NeverShortCircuit(f(a))` when const closures added. +pub(crate) struct Wrapped T> { + f: F, + p: PhantomData<(T, A)>, +} +#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")] +impl T + [const] Destruct> const FnOnce<(A,)> for Wrapped { + type Output = NeverShortCircuit; + + extern "rust-call" fn call_once(mut self, args: (A,)) -> Self::Output { + self.call_mut(args) + } +} +#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")] +impl T> const FnMut<(A,)> for Wrapped { + extern "rust-call" fn call_mut(&mut self, (args,): (A,)) -> Self::Output { + NeverShortCircuit((self.f)(args)) + } +} impl NeverShortCircuit { /// Wraps a unary function to produce one that wraps the output into a `NeverShortCircuit`. @@ -403,10 +424,11 @@ impl NeverShortCircuit { /// This is useful for implementing infallible functions in terms of the `try_` ones, /// without accidentally capturing extra generic parameters in a closure. #[inline] - pub(crate) fn wrap_mut_1( - mut f: impl FnMut(A) -> T, - ) -> impl FnMut(A) -> NeverShortCircuit { - move |a| NeverShortCircuit(f(a)) + pub(crate) const fn wrap_mut_1(f: F) -> Wrapped + where + F: [const] FnMut(A) -> T, + { + Wrapped { f, p: PhantomData } } #[inline] @@ -417,7 +439,8 @@ impl NeverShortCircuit { pub(crate) enum NeverShortCircuitResidual {} -impl Try for NeverShortCircuit { +#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")] +impl const Try for NeverShortCircuit { type Output = T; type Residual = NeverShortCircuitResidual; @@ -431,15 +454,15 @@ impl Try for NeverShortCircuit { NeverShortCircuit(x) } } - -impl FromResidual for NeverShortCircuit { +#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")] +impl const FromResidual for NeverShortCircuit { #[inline] fn from_residual(never: NeverShortCircuitResidual) -> Self { match never {} } } - -impl Residual for NeverShortCircuitResidual { +#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")] +impl const Residual for NeverShortCircuitResidual { type TryType = NeverShortCircuit; } diff --git a/library/coretests/tests/array.rs b/library/coretests/tests/array.rs index c4a8fc74feca3..2b4429092e98b 100644 --- a/library/coretests/tests/array.rs +++ b/library/coretests/tests/array.rs @@ -724,3 +724,20 @@ fn array_eq() { let not_true = [0u8] == [].as_slice(); assert!(!not_true); } + +#[test] +fn const_array_ops() { + const fn doubler(x: usize) -> usize { + x * 2 + } + const fn maybe_doubler(x: usize) -> Option { + x.checked_mul(2) + } + assert_eq!(const { std::array::from_fn::<_, 5, _>(doubler) }, [0, 2, 4, 6, 8]); + assert_eq!(const { [5, 6, 1, 2].map(doubler) }, [10, 12, 2, 4]); + assert_eq!(const { [1, usize::MAX, 2, 8].try_map(maybe_doubler) }, None); + assert_eq!(const { std::array::try_from_fn::<_, 5, _>(maybe_doubler) }, Some([0, 2, 4, 6, 8])); + #[derive(Debug, PartialEq)] + struct Zst; + assert_eq!([(); 10].try_map(|()| Some(Zst)), Some([const { Zst }; 10])); +} diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 48982d71e8137..436856635c1c8 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -4,6 +4,7 @@ #![feature(alloc_layout_extra)] #![feature(array_ptr_get)] #![feature(array_try_from_fn)] +#![feature(array_try_map)] #![feature(array_windows)] #![feature(ascii_char)] #![feature(ascii_char_variants)] @@ -16,6 +17,7 @@ #![feature(char_internals)] #![feature(char_max_len)] #![feature(clone_to_uninit)] +#![feature(const_array)] #![feature(const_cell_traits)] #![feature(const_cmp)] #![feature(const_convert)]