Skip to content

Commit b1b3e5e

Browse files
authored
Rollup merge of #147071 - bend-n:const_array-ops, r=oli-obk
constify from_fn, try_from_fn, try_map, map adds the `const_array` feature reimplements `try_map` in more or less the same way
2 parents cf8a955 + e3a2c23 commit b1b3e5e

File tree

5 files changed

+179
-94
lines changed

5 files changed

+179
-94
lines changed

library/core/src/array/drain.rs

Lines changed: 94 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,108 @@
1-
use crate::iter::{TrustedLen, UncheckedIterator};
2-
use crate::mem::ManuallyDrop;
3-
use crate::ptr::drop_in_place;
4-
use crate::slice;
1+
use crate::marker::{Destruct, PhantomData};
2+
use crate::mem::{ManuallyDrop, SizedTypeProperties, conjure_zst};
3+
use crate::ptr::{NonNull, drop_in_place, from_raw_parts_mut, null_mut};
54

6-
/// A situationally-optimized version of `array.into_iter().for_each(func)`.
7-
///
8-
/// [`crate::array::IntoIter`]s are great when you need an owned iterator, but
9-
/// storing the entire array *inside* the iterator like that can sometimes
10-
/// pessimize code. Notable, it can be more bytes than you really want to move
11-
/// around, and because the array accesses index into it SRoA has a harder time
12-
/// optimizing away the type than it does iterators that just hold a couple pointers.
13-
///
14-
/// Thus this function exists, which gives a way to get *moved* access to the
15-
/// elements of an array using a small iterator -- no bigger than a slice iterator.
16-
///
17-
/// The function-taking-a-closure structure makes it safe, as it keeps callers
18-
/// from looking at already-dropped elements.
19-
pub(crate) fn drain_array_with<T, R, const N: usize>(
20-
array: [T; N],
21-
func: impl for<'a> FnOnce(Drain<'a, T>) -> R,
22-
) -> R {
23-
let mut array = ManuallyDrop::new(array);
24-
// SAFETY: Now that the local won't drop it, it's ok to construct the `Drain` which will.
25-
let drain = Drain(array.iter_mut());
26-
func(drain)
5+
impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> {
6+
/// This function returns a function that lets you index the given array in const.
7+
/// As implemented it can optimize better than iterators, and can be constified.
8+
/// It acts like a sort of guard (owns the array) and iterator combined, which can be implemented
9+
/// as it is a struct that implements const fn;
10+
/// in that regard it is somewhat similar to an array::Iter implementing `UncheckedIterator`.
11+
/// The only method you're really allowed to call is `next()`,
12+
/// anything else is more or less UB, hence this function being unsafe.
13+
/// Moved elements will not be dropped.
14+
/// This will also not actually store the array.
15+
///
16+
/// SAFETY: must only be called `N` times. Thou shalt not drop the array either.
17+
// FIXME(const-hack): this is a hack for `let guard = Guard(array); |i| f(guard[i])`.
18+
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
19+
pub(super) const unsafe fn new(array: &'l mut ManuallyDrop<[T; N]>, f: &'f mut F) -> Self {
20+
// dont drop the array, transfers "ownership" to Self
21+
let ptr: NonNull<T> = NonNull::from_mut(array).cast();
22+
// SAFETY:
23+
// Adding `slice.len()` to the starting pointer gives a pointer
24+
// at the end of `slice`. `end` will never be dereferenced, only checked
25+
// for direct pointer equality with `ptr` to check if the drainer is done.
26+
unsafe {
27+
let end = if T::IS_ZST { null_mut() } else { ptr.as_ptr().add(N) };
28+
Self { ptr, end, f, l: PhantomData }
29+
}
30+
}
2731
}
2832

29-
/// See [`drain_array_with`] -- this is `pub(crate)` only so it's allowed to be
30-
/// mentioned in the signature of that method. (Otherwise it hits `E0446`.)
31-
// INVARIANT: It's ok to drop the remainder of the inner iterator.
32-
pub(crate) struct Drain<'a, T>(slice::IterMut<'a, T>);
33+
/// See [`Drain::new`]; this is our fake iterator.
34+
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
35+
#[unstable(feature = "array_try_map", issue = "79711")]
36+
pub(super) struct Drain<'l, 'f, T, const N: usize, F> {
37+
// FIXME(const-hack): This is essentially a slice::IterMut<'static>, replace when possible.
38+
/// The pointer to the next element to return, or the past-the-end location
39+
/// if the drainer is empty.
40+
///
41+
/// This address will be used for all ZST elements, never changed.
42+
/// As we "own" this array, we dont need to store any lifetime.
43+
ptr: NonNull<T>,
44+
/// For non-ZSTs, the non-null pointer to the past-the-end element.
45+
/// For ZSTs, this is null.
46+
end: *mut T,
3347

34-
impl<T> Drop for Drain<'_, T> {
35-
fn drop(&mut self) {
36-
// SAFETY: By the type invariant, we're allowed to drop all these.
37-
unsafe { drop_in_place(self.0.as_mut_slice()) }
38-
}
48+
f: &'f mut F,
49+
l: PhantomData<&'l mut [T; N]>,
3950
}
4051

41-
impl<T> Iterator for Drain<'_, T> {
42-
type Item = T;
43-
44-
#[inline]
45-
fn next(&mut self) -> Option<T> {
46-
let p: *const T = self.0.next()?;
47-
// SAFETY: The iterator was already advanced, so we won't drop this later.
48-
Some(unsafe { p.read() })
49-
}
52+
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
53+
#[unstable(feature = "array_try_map", issue = "79711")]
54+
impl<T, U, const N: usize, F> const FnOnce<(usize,)> for &mut Drain<'_, '_, T, N, F>
55+
where
56+
F: [const] FnMut(T) -> U,
57+
{
58+
type Output = U;
5059

51-
#[inline]
52-
fn size_hint(&self) -> (usize, Option<usize>) {
53-
let n = self.len();
54-
(n, Some(n))
60+
/// This implementation is useless.
61+
extern "rust-call" fn call_once(mut self, args: (usize,)) -> Self::Output {
62+
self.call_mut(args)
5563
}
5664
}
57-
58-
impl<T> ExactSizeIterator for Drain<'_, T> {
59-
#[inline]
60-
fn len(&self) -> usize {
61-
self.0.len()
65+
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
66+
#[unstable(feature = "array_try_map", issue = "79711")]
67+
impl<T, U, const N: usize, F> const FnMut<(usize,)> for &mut Drain<'_, '_, T, N, F>
68+
where
69+
F: [const] FnMut(T) -> U,
70+
{
71+
// FIXME(const-hack): ideally this would be an unsafe fn `next()`, and to use it you would instead `|_| unsafe { drain.next() }`.
72+
extern "rust-call" fn call_mut(
73+
&mut self,
74+
(_ /* ignore argument */,): (usize,),
75+
) -> Self::Output {
76+
if T::IS_ZST {
77+
// its UB to call this more than N times, so returning more ZSTs is valid.
78+
// SAFETY: its a ZST? we conjur.
79+
(self.f)(unsafe { conjure_zst::<T>() })
80+
} else {
81+
// increment before moving; if `f` panics, we drop the rest.
82+
let p = self.ptr;
83+
// SAFETY: caller guarantees never called more than N times (see `Drain::new`)
84+
self.ptr = unsafe { self.ptr.add(1) };
85+
// SAFETY: we are allowed to move this.
86+
(self.f)(unsafe { p.read() })
87+
}
6288
}
6389
}
90+
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
91+
#[unstable(feature = "array_try_map", issue = "79711")]
92+
impl<T: [const] Destruct, const N: usize, F> const Drop for Drain<'_, '_, T, N, F> {
93+
fn drop(&mut self) {
94+
if !T::IS_ZST {
95+
// SAFETY: we cant read more than N elements
96+
let slice = unsafe {
97+
from_raw_parts_mut::<[T]>(
98+
self.ptr.as_ptr(),
99+
// SAFETY: `start <= end`
100+
self.end.offset_from_unsigned(self.ptr.as_ptr()),
101+
)
102+
};
64103

65-
// SAFETY: This is a 1:1 wrapper for a slice iterator, which is also `TrustedLen`.
66-
unsafe impl<T> TrustedLen for Drain<'_, T> {}
67-
68-
impl<T> UncheckedIterator for Drain<'_, T> {
69-
unsafe fn next_unchecked(&mut self) -> T {
70-
// SAFETY: `Drain` is 1:1 with the inner iterator, so if the caller promised
71-
// that there's an element left, the inner iterator has one too.
72-
let p: *const T = unsafe { self.0.next_unchecked() };
73-
// SAFETY: The iterator was already advanced, so we won't drop this later.
74-
unsafe { p.read() }
104+
// SAFETY: By the type invariant, we're allowed to drop all these. (we own it, after all)
105+
unsafe { drop_in_place(slice) }
106+
}
75107
}
76108
}

library/core/src/array/mod.rs

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use crate::error::Error;
1212
use crate::hash::{self, Hash};
1313
use crate::intrinsics::transmute_unchecked;
1414
use crate::iter::{UncheckedIterator, repeat_n};
15-
use crate::mem::{self, MaybeUninit};
15+
use crate::marker::Destruct;
16+
use crate::mem::{self, ManuallyDrop, MaybeUninit};
1617
use crate::ops::{
1718
ChangeOutputType, ControlFlow, FromResidual, Index, IndexMut, NeverShortCircuit, Residual, Try,
1819
};
@@ -25,7 +26,6 @@ mod drain;
2526
mod equality;
2627
mod iter;
2728

28-
pub(crate) use drain::drain_array_with;
2929
#[stable(feature = "array_value_iter", since = "1.51.0")]
3030
pub use iter::IntoIter;
3131

@@ -105,9 +105,10 @@ pub fn repeat<T: Clone, const N: usize>(val: T) -> [T; N] {
105105
/// ```
106106
#[inline]
107107
#[stable(feature = "array_from_fn", since = "1.63.0")]
108-
pub fn from_fn<T, const N: usize, F>(f: F) -> [T; N]
108+
#[rustc_const_unstable(feature = "const_array", issue = "147606")]
109+
pub const fn from_fn<T: [const] Destruct, const N: usize, F>(f: F) -> [T; N]
109110
where
110-
F: FnMut(usize) -> T,
111+
F: [const] FnMut(usize) -> T + [const] Destruct,
111112
{
112113
try_from_fn(NeverShortCircuit::wrap_mut_1(f)).0
113114
}
@@ -143,11 +144,11 @@ where
143144
/// ```
144145
#[inline]
145146
#[unstable(feature = "array_try_from_fn", issue = "89379")]
146-
pub fn try_from_fn<R, const N: usize, F>(cb: F) -> ChangeOutputType<R, [R::Output; N]>
147+
#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")]
148+
pub const fn try_from_fn<R, const N: usize, F>(cb: F) -> ChangeOutputType<R, [R::Output; N]>
147149
where
148-
F: FnMut(usize) -> R,
149-
R: Try,
150-
R::Residual: Residual<[R::Output; N]>,
150+
R: [const] Try<Residual: [const] Residual<[R::Output; N]>, Output: [const] Destruct>,
151+
F: [const] FnMut(usize) -> R + [const] Destruct,
151152
{
152153
let mut array = [const { MaybeUninit::uninit() }; N];
153154
match try_from_fn_erased(&mut array, cb) {
@@ -549,9 +550,12 @@ impl<T, const N: usize> [T; N] {
549550
/// ```
550551
#[must_use]
551552
#[stable(feature = "array_map", since = "1.55.0")]
552-
pub fn map<F, U>(self, f: F) -> [U; N]
553+
#[rustc_const_unstable(feature = "const_array", issue = "147606")]
554+
pub const fn map<F, U>(self, f: F) -> [U; N]
553555
where
554-
F: FnMut(T) -> U,
556+
F: [const] FnMut(T) -> U + [const] Destruct,
557+
U: [const] Destruct,
558+
T: [const] Destruct,
555559
{
556560
self.try_map(NeverShortCircuit::wrap_mut_1(f)).0
557561
}
@@ -587,11 +591,19 @@ impl<T, const N: usize> [T; N] {
587591
/// assert_eq!(c, Some(a));
588592
/// ```
589593
#[unstable(feature = "array_try_map", issue = "79711")]
590-
pub fn try_map<R>(self, f: impl FnMut(T) -> R) -> ChangeOutputType<R, [R::Output; N]>
594+
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
595+
pub const fn try_map<R>(
596+
self,
597+
mut f: impl [const] FnMut(T) -> R + [const] Destruct,
598+
) -> ChangeOutputType<R, [R::Output; N]>
591599
where
592-
R: Try<Residual: Residual<[R::Output; N]>>,
600+
R: [const] Try<Residual: [const] Residual<[R::Output; N]>, Output: [const] Destruct>,
601+
T: [const] Destruct,
593602
{
594-
drain_array_with(self, |iter| try_from_trusted_iterator(iter.map(f)))
603+
let mut me = ManuallyDrop::new(self);
604+
// SAFETY: try_from_fn calls `f` N times.
605+
let mut f = unsafe { drain::Drain::new(&mut me, &mut f) };
606+
try_from_fn(&mut f)
595607
}
596608

597609
/// Returns a slice containing the entire array. Equivalent to `&s[..]`.
@@ -885,13 +897,11 @@ where
885897
/// not optimizing away. So if you give it a shot, make sure to watch what
886898
/// happens in the codegen tests.
887899
#[inline]
888-
fn try_from_fn_erased<T, R>(
889-
buffer: &mut [MaybeUninit<T>],
890-
mut generator: impl FnMut(usize) -> R,
891-
) -> ControlFlow<R::Residual>
892-
where
893-
R: Try<Output = T>,
894-
{
900+
#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")]
901+
const fn try_from_fn_erased<R: [const] Try<Output: [const] Destruct>>(
902+
buffer: &mut [MaybeUninit<R::Output>],
903+
mut generator: impl [const] FnMut(usize) -> R + [const] Destruct,
904+
) -> ControlFlow<R::Residual> {
895905
let mut guard = Guard { array_mut: buffer, initialized: 0 };
896906

897907
while guard.initialized < guard.array_mut.len() {
@@ -930,7 +940,8 @@ impl<T> Guard<'_, T> {
930940
///
931941
/// No more than N elements must be initialized.
932942
#[inline]
933-
pub(crate) unsafe fn push_unchecked(&mut self, item: T) {
943+
#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")]
944+
pub(crate) const unsafe fn push_unchecked(&mut self, item: T) {
934945
// SAFETY: If `initialized` was correct before and the caller does not
935946
// invoke this method more than N times then writes will be in-bounds
936947
// and slots will not be initialized more than once.
@@ -941,11 +952,11 @@ impl<T> Guard<'_, T> {
941952
}
942953
}
943954

944-
impl<T> Drop for Guard<'_, T> {
955+
#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")]
956+
impl<T: [const] Destruct> const Drop for Guard<'_, T> {
945957
#[inline]
946958
fn drop(&mut self) {
947959
debug_assert!(self.initialized <= self.array_mut.len());
948-
949960
// SAFETY: this slice will contain only initialized objects.
950961
unsafe {
951962
self.array_mut.get_unchecked_mut(..self.initialized).assume_init_drop();

library/core/src/ops/try_trait.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::marker::{Destruct, PhantomData};
12
use crate::ops::ControlFlow;
23

34
/// The `?` operator and `try {}` blocks.
@@ -363,6 +364,7 @@ where
363364
pub const trait Residual<O>: Sized {
364365
/// The "return" type of this meta-function.
365366
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
367+
// FIXME: ought to be implied
366368
type TryType: [const] Try<Output = O, Residual = Self>;
367369
}
368370

@@ -396,17 +398,37 @@ pub(crate) type ChangeOutputType<T: Try<Residual: Residual<V>>, V> =
396398
/// Not currently planned to be exposed publicly, so just `pub(crate)`.
397399
#[repr(transparent)]
398400
pub(crate) struct NeverShortCircuit<T>(pub T);
401+
// FIXME(const-hack): replace with `|a| NeverShortCircuit(f(a))` when const closures added.
402+
pub(crate) struct Wrapped<T, A, F: FnMut(A) -> T> {
403+
f: F,
404+
p: PhantomData<(T, A)>,
405+
}
406+
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
407+
impl<T, A, F: [const] FnMut(A) -> T + [const] Destruct> const FnOnce<(A,)> for Wrapped<T, A, F> {
408+
type Output = NeverShortCircuit<T>;
409+
410+
extern "rust-call" fn call_once(mut self, args: (A,)) -> Self::Output {
411+
self.call_mut(args)
412+
}
413+
}
414+
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
415+
impl<T, A, F: [const] FnMut(A) -> T> const FnMut<(A,)> for Wrapped<T, A, F> {
416+
extern "rust-call" fn call_mut(&mut self, (args,): (A,)) -> Self::Output {
417+
NeverShortCircuit((self.f)(args))
418+
}
419+
}
399420

400421
impl<T> NeverShortCircuit<T> {
401422
/// Wraps a unary function to produce one that wraps the output into a `NeverShortCircuit`.
402423
///
403424
/// This is useful for implementing infallible functions in terms of the `try_` ones,
404425
/// without accidentally capturing extra generic parameters in a closure.
405426
#[inline]
406-
pub(crate) fn wrap_mut_1<A>(
407-
mut f: impl FnMut(A) -> T,
408-
) -> impl FnMut(A) -> NeverShortCircuit<T> {
409-
move |a| NeverShortCircuit(f(a))
427+
pub(crate) const fn wrap_mut_1<A, F>(f: F) -> Wrapped<T, A, F>
428+
where
429+
F: [const] FnMut(A) -> T,
430+
{
431+
Wrapped { f, p: PhantomData }
410432
}
411433

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

418440
pub(crate) enum NeverShortCircuitResidual {}
419441

420-
impl<T> Try for NeverShortCircuit<T> {
442+
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
443+
impl<T> const Try for NeverShortCircuit<T> {
421444
type Output = T;
422445
type Residual = NeverShortCircuitResidual;
423446

@@ -431,15 +454,15 @@ impl<T> Try for NeverShortCircuit<T> {
431454
NeverShortCircuit(x)
432455
}
433456
}
434-
435-
impl<T> FromResidual for NeverShortCircuit<T> {
457+
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
458+
impl<T> const FromResidual for NeverShortCircuit<T> {
436459
#[inline]
437460
fn from_residual(never: NeverShortCircuitResidual) -> Self {
438461
match never {}
439462
}
440463
}
441-
442-
impl<T> Residual<T> for NeverShortCircuitResidual {
464+
#[rustc_const_unstable(feature = "const_never_short_circuit", issue = "none")]
465+
impl<T: [const] Destruct> const Residual<T> for NeverShortCircuitResidual {
443466
type TryType = NeverShortCircuit<T>;
444467
}
445468

0 commit comments

Comments
 (0)