Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 95 additions & 62 deletions library/core/src/array/drain.rs
Original file line number Diff line number Diff line change
@@ -1,76 +1,109 @@
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, null_mut};
use crate::slice::from_raw_parts_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<T, R, const N: usize>(
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<T> = 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<T>,
/// For non-ZSTs, the non-null pointer to the past-the-end element.
/// For ZSTs, this is null.
end: *mut T,

impl<T> 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<T> Iterator for Drain<'_, T> {
type Item = T;

#[inline]
fn next(&mut self) -> Option<T> {
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<T, U, const N: usize, F> 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<usize>) {
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<T> 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<T, U, const N: usize, F> 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::<T>() })
} 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<T: [const] Destruct, const N: usize, F> 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(
self.ptr.as_mut(),
// 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<T> TrustedLen for Drain<'_, T> {}

impl<T> 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) }
}
}
}
57 changes: 34 additions & 23 deletions library/core/src/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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;

Expand Down Expand Up @@ -105,9 +105,10 @@ pub fn repeat<T: Clone, const N: usize>(val: T) -> [T; N] {
/// ```
#[inline]
#[stable(feature = "array_from_fn", since = "1.63.0")]
pub fn from_fn<T, const N: usize, F>(f: F) -> [T; N]
#[rustc_const_unstable(feature = "const_array", issue = "147606")]
pub const fn from_fn<T: [const] Destruct, const N: usize, F>(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
}
Expand Down Expand Up @@ -143,11 +144,11 @@ where
/// ```
#[inline]
#[unstable(feature = "array_try_from_fn", issue = "89379")]
pub fn try_from_fn<R, const N: usize, F>(cb: F) -> ChangeOutputType<R, [R::Output; N]>
#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")]
pub const fn try_from_fn<R, const N: usize, F>(cb: F) -> ChangeOutputType<R, [R::Output; N]>
where
F: FnMut(usize) -> R,
R: Try,
R::Residual: Residual<[R::Output; N]>,
R: [const] Try<Residual: [const] Residual<[R::Output; N]>, 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) {
Expand Down Expand Up @@ -549,9 +550,12 @@ impl<T, const N: usize> [T; N] {
/// ```
#[must_use]
#[stable(feature = "array_map", since = "1.55.0")]
pub fn map<F, U>(self, f: F) -> [U; N]
#[rustc_const_unstable(feature = "const_array", issue = "147606")]
pub const fn map<F, U>(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
}
Expand Down Expand Up @@ -587,11 +591,19 @@ impl<T, const N: usize> [T; N] {
/// assert_eq!(c, Some(a));
/// ```
#[unstable(feature = "array_try_map", issue = "79711")]
pub fn try_map<R>(self, f: impl FnMut(T) -> R) -> ChangeOutputType<R, [R::Output; N]>
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
pub const fn try_map<R>(
self,
mut f: impl [const] FnMut(T) -> R + [const] Destruct,
) -> ChangeOutputType<R, [R::Output; N]>
where
R: Try<Residual: Residual<[R::Output; N]>>,
R: [const] Try<Residual: [const] Residual<[R::Output; N]>, 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[..]`.
Expand Down Expand Up @@ -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<T, R>(
buffer: &mut [MaybeUninit<T>],
mut generator: impl FnMut(usize) -> R,
) -> ControlFlow<R::Residual>
where
R: Try<Output = T>,
{
#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")]
const fn try_from_fn_erased<R: [const] Try<Output: [const] Destruct>>(
buffer: &mut [MaybeUninit<R::Output>],
mut generator: impl [const] FnMut(usize) -> R + [const] Destruct,
) -> ControlFlow<R::Residual> {
let mut guard = Guard { array_mut: buffer, initialized: 0 };

while guard.initialized < guard.array_mut.len() {
Expand Down Expand Up @@ -930,7 +940,8 @@ impl<T> 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.
Expand All @@ -941,11 +952,11 @@ impl<T> Guard<'_, T> {
}
}

impl<T> Drop for Guard<'_, T> {
#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")]
impl<T: [const] Destruct> 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();
Expand Down
41 changes: 32 additions & 9 deletions library/core/src/ops/try_trait.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::marker::{Destruct, PhantomData};
use crate::ops::ControlFlow;

/// The `?` operator and `try {}` blocks.
Expand Down Expand Up @@ -363,6 +364,7 @@ where
pub const trait Residual<O>: 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<Output = O, Residual = Self>;
}

Expand Down Expand Up @@ -396,17 +398,37 @@ pub(crate) type ChangeOutputType<T: Try<Residual: Residual<V>>, V> =
/// Not currently planned to be exposed publicly, so just `pub(crate)`.
#[repr(transparent)]
pub(crate) struct NeverShortCircuit<T>(pub T);
// FIXME(const-hack): replace with `|a| NeverShortCircuit(f(a))` when const closures added.
pub(crate) struct Wrapped<T, A, F: FnMut(A) -> T> {
f: F,
p: PhantomData<(T, A)>,
}
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
impl<T, A, F: [const] FnMut(A) -> T + [const] Destruct> const FnOnce<(A,)> for Wrapped<T, A, F> {
type Output = NeverShortCircuit<T>;

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, A, F: [const] FnMut(A) -> T> const FnMut<(A,)> for Wrapped<T, A, F> {
extern "rust-call" fn call_mut(&mut self, (args,): (A,)) -> Self::Output {
NeverShortCircuit((self.f)(args))
}
}

impl<T> NeverShortCircuit<T> {
/// Wraps a unary function to produce one that wraps the output into a `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<A>(
mut f: impl FnMut(A) -> T,
) -> impl FnMut(A) -> NeverShortCircuit<T> {
move |a| NeverShortCircuit(f(a))
pub(crate) const fn wrap_mut_1<A, F>(f: F) -> Wrapped<T, A, F>
where
F: [const] FnMut(A) -> T,
{
Wrapped { f, p: PhantomData }
}

#[inline]
Expand All @@ -417,7 +439,8 @@ impl<T> NeverShortCircuit<T> {

pub(crate) enum NeverShortCircuitResidual {}

impl<T> Try for NeverShortCircuit<T> {
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
impl<T> const Try for NeverShortCircuit<T> {
type Output = T;
type Residual = NeverShortCircuitResidual;

Expand All @@ -431,15 +454,15 @@ impl<T> Try for NeverShortCircuit<T> {
NeverShortCircuit(x)
}
}

impl<T> FromResidual for NeverShortCircuit<T> {
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
impl<T> const FromResidual for NeverShortCircuit<T> {
#[inline]
fn from_residual(never: NeverShortCircuitResidual) -> Self {
match never {}
}
}

impl<T> Residual<T> for NeverShortCircuitResidual {
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
impl<T: [const] Destruct> const Residual<T> for NeverShortCircuitResidual {
type TryType = NeverShortCircuit<T>;
}

Expand Down
Loading
Loading