Skip to content

Commit 83d9e8c

Browse files
committed
redo the drain
1 parent fb0881f commit 83d9e8c

File tree

4 files changed

+95
-65
lines changed

4 files changed

+95
-65
lines changed

library/core/src/array/drain.rs

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,108 @@
1-
use crate::assert_unsafe_precondition;
2-
use crate::marker::Destruct;
3-
use crate::mem::ManuallyDrop;
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};
44

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+
}
31+
}
32+
33+
/// See [`Drain::new`]; this is our fake iterator.
534
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
635
#[unstable(feature = "array_try_map", issue = "79711")]
7-
pub(super) struct Drain<'a, T, U, const N: usize, F: FnMut(T) -> U> {
8-
array: ManuallyDrop<[T; N]>,
9-
moved: usize,
10-
f: &'a mut F,
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,
47+
48+
f: &'f mut F,
49+
l: PhantomData<&'l mut [T; N]>,
1150
}
51+
1252
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
1353
#[unstable(feature = "array_try_map", issue = "79711")]
14-
impl<T, U, const N: usize, F> const FnOnce<(usize,)> for &mut Drain<'_, T, U, N, F>
54+
impl<T, U, const N: usize, F> const FnOnce<(usize,)> for &mut Drain<'_, '_, T, N, F>
1555
where
1656
F: [const] FnMut(T) -> U,
1757
{
1858
type Output = U;
1959

60+
/// This implementation is useless.
2061
extern "rust-call" fn call_once(mut self, args: (usize,)) -> Self::Output {
2162
self.call_mut(args)
2263
}
2364
}
2465
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
2566
#[unstable(feature = "array_try_map", issue = "79711")]
26-
impl<T, U, const N: usize, F> const FnMut<(usize,)> for &mut Drain<'_, T, U, N, F>
67+
impl<T, U, const N: usize, F> const FnMut<(usize,)> for &mut Drain<'_, '_, T, N, F>
2768
where
2869
F: [const] FnMut(T) -> U,
2970
{
30-
extern "rust-call" fn call_mut(&mut self, (i,): (usize,)) -> Self::Output {
31-
// SAFETY: increment moved before moving. if `f` panics, we drop the rest.
32-
self.moved += 1;
33-
assert_unsafe_precondition!(
34-
check_library_ub,
35-
"musnt index array out of bounds", (i: usize = i, size: usize = N) => i < size
36-
);
37-
// SAFETY: the `i` should also always go up, and musnt skip any, else some things will be leaked.
38-
// SAFETY: if it goes down, we will drop freed elements. not good.
39-
// SAFETY: caller guarantees never called with number >= N (see `Drain::new`)
40-
(self.f)(unsafe { self.array.as_ptr().add(i).read() })
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+
}
4188
}
4289
}
4390
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
4491
#[unstable(feature = "array_try_map", issue = "79711")]
45-
impl<T: [const] Destruct, U, const N: usize, F: FnMut(T) -> U> const Drop
46-
for Drain<'_, T, U, N, F>
47-
{
92+
impl<T: [const] Destruct, const N: usize, F> const Drop for Drain<'_, '_, T, N, F> {
4893
fn drop(&mut self) {
49-
let mut n = self.moved;
50-
while n != N {
51-
// SAFETY: moved must always be < N
52-
unsafe { self.array.as_mut_ptr().add(n).drop_in_place() };
53-
n += 1;
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,
99+
// SAFETY: `start <= end`
100+
self.end.offset_from_unsigned(self.ptr.as_ptr()),
101+
)
102+
};
103+
104+
// SAFETY: By the type invariant, we're allowed to drop all these. (we own it, after all)
105+
unsafe { drop_in_place(slice) }
54106
}
55107
}
56108
}
57-
impl<'a, T, U, const N: usize, F: FnMut(T) -> U> Drain<'a, T, U, N, F> {
58-
/// This function returns a function that lets you index the given array in const.
59-
/// As implemented it can optimize better than iterators, and can be constified.
60-
/// It acts like a sort of guard and iterator combined, which can be implemented
61-
/// as it is a struct that implements const fn;
62-
/// in that regard it is somewhat similar to an array::Iter implementing `UncheckedIterator`.
63-
/// The only method you're really allowed to call is `next()`,
64-
/// anything else is more or less UB, hence this function being unsafe.
65-
/// Moved elements will not be dropped.
66-
///
67-
/// Previously this was implemented as a wrapper around a `slice::Iter`, which
68-
/// called `read()` on the returned `&T`; gnarly stuff.
69-
///
70-
/// SAFETY: must be called in order of 0..N, without indexing out of bounds. (see `Drain::call_mut`)
71-
/// Potentially the function could completely disregard the supplied argument, however i think that behaviour would be unintuitive.
72-
// FIXME(const-hack): this is a hack for `let guard = Guard(array); |i| f(guard[i])`.
73-
pub(super) const unsafe fn new(array: [T; N], f: &'a mut F) -> Self {
74-
Self { array: ManuallyDrop::new(array), moved: 0, f }
75-
}
76-
}

library/core/src/array/mod.rs

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::hash::{self, Hash};
1313
use crate::intrinsics::transmute_unchecked;
1414
use crate::iter::{UncheckedIterator, repeat_n};
1515
use crate::marker::Destruct;
16-
use crate::mem::{self, MaybeUninit};
16+
use crate::mem::{self, ManuallyDrop, MaybeUninit};
1717
use crate::ops::{
1818
ChangeOutputType, ControlFlow, FromResidual, Index, IndexMut, NeverShortCircuit, Residual, Try,
1919
};
@@ -145,14 +145,10 @@ where
145145
#[inline]
146146
#[unstable(feature = "array_try_from_fn", issue = "89379")]
147147
#[rustc_const_unstable(feature = "array_try_from_fn", issue = "89379")]
148-
pub const fn try_from_fn<R, const N: usize>(
149-
cb: impl [const] FnMut(usize) -> R + [const] Destruct,
150-
) -> ChangeOutputType<R, [R::Output; N]>
148+
pub const fn try_from_fn<R, const N: usize, F>(cb: F) -> ChangeOutputType<R, [R::Output; N]>
151149
where
152-
R: [const] Try<
153-
Residual: [const] Residual<[R::Output; N], TryType: [const] Try>,
154-
Output: [const] Destruct,
155-
>,
150+
R: [const] Try<Residual: [const] Residual<[R::Output; N]>, Output: [const] Destruct>,
151+
F: [const] FnMut(usize) -> R + [const] Destruct,
156152
{
157153
let mut array = [const { MaybeUninit::uninit() }; N];
158154
match try_from_fn_erased(&mut array, cb) {
@@ -559,6 +555,7 @@ impl<T, const N: usize> [T; N] {
559555
where
560556
F: [const] FnMut(T) -> U + [const] Destruct,
561557
U: [const] Destruct,
558+
T: [const] Destruct,
562559
{
563560
self.try_map(NeverShortCircuit::wrap_mut_1(f)).0
564561
}
@@ -600,16 +597,13 @@ impl<T, const N: usize> [T; N] {
600597
mut f: impl [const] FnMut(T) -> R + [const] Destruct,
601598
) -> ChangeOutputType<R, [R::Output; N]>
602599
where
603-
R: [const] Try<
604-
Residual: [const] Residual<[R::Output; N], TryType: [const] Try>,
605-
Output: [const] Destruct,
606-
>,
600+
R: [const] Try<Residual: [const] Residual<[R::Output; N]>, Output: [const] Destruct>,
601+
T: [const] Destruct,
607602
{
608-
// SAFETY: try_from_fn calls `f` with 0..N.
609-
let mut f = unsafe { drain::Drain::new(self, &mut f) };
610-
let out = try_from_fn(&mut f);
611-
mem::forget(f); // it doesnt like being remembered
612-
out
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)
613607
}
614608

615609
/// Returns a slice containing the entire array. Equivalent to `&s[..]`.

library/core/src/ops/try_trait.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ where
364364
pub const trait Residual<O>: Sized {
365365
/// The "return" type of this meta-function.
366366
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
367+
// FIXME: ought to be implied
367368
type TryType: [const] Try<Output = O, Residual = Self>;
368369
}
369370

library/coretests/tests/array.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,4 +737,7 @@ fn const_array_ops() {
737737
assert_eq!(const { [5, 6, 1, 2].map(doubler) }, [10, 12, 2, 4]);
738738
assert_eq!(const { [1, usize::MAX, 2, 8].try_map(maybe_doubler) }, None);
739739
assert_eq!(const { std::array::try_from_fn::<_, 5, _>(maybe_doubler) }, Some([0, 2, 4, 6, 8]));
740+
#[derive(Debug, PartialEq)]
741+
struct Zst;
742+
assert_eq!([(); 10].try_map(|()| Some(Zst)), Some([const { Zst }; 10]));
740743
}

0 commit comments

Comments
 (0)