Skip to content

Commit b9ad3a9

Browse files
committed
core: drop unmapped ZSTs in array map
1 parent f21b4c0 commit b9ad3a9

3 files changed

Lines changed: 75 additions & 22 deletions

File tree

library/core/src/array/drain.rs

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
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};
2+
use crate::mem::{ManuallyDrop, SizedTypeProperties, conjure_zst, transmute};
3+
use crate::ptr::{NonNull, drop_in_place, from_raw_parts_mut, without_provenance_mut};
44

5-
impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> {
5+
impl<'l, 'f, T, U, F: FnMut(T) -> U> Drain<'l, 'f, T, F> {
66
/// This function returns a function that lets you index the given array in const.
77
/// As implemented it can optimize better than iterators, and can be constified.
88
/// It acts like a sort of guard (owns the array) and iterator combined, which can be implemented
@@ -14,44 +14,47 @@ impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> {
1414
/// This will also not actually store the array.
1515
///
1616
/// 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])`.
1817
#[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 {
18+
pub(super) const unsafe fn new<const N: usize>(
19+
array: &'l mut ManuallyDrop<[T; N]>,
20+
f: &'f mut F,
21+
) -> Self {
2022
// dont drop the array, transfers "ownership" to Self
2123
let ptr: NonNull<T> = NonNull::from_mut(array).cast();
2224
// SAFETY:
2325
// Adding `slice.len()` to the starting pointer gives a pointer
2426
// at the end of `slice`. `end` will never be dereferenced, only checked
2527
// for direct pointer equality with `ptr` to check if the drainer is done.
2628
unsafe {
27-
let end = if T::IS_ZST { null_mut() } else { ptr.as_ptr().add(N) };
28-
Self { ptr, end, f, l: PhantomData }
29+
let end_or_len =
30+
if T::IS_ZST { without_provenance_mut(N) } else { ptr.as_ptr().add(N) };
31+
Self { ptr, end_or_len, f, l: PhantomData }
2932
}
3033
}
3134
}
3235

3336
/// See [`Drain::new`]; this is our fake iterator.
3437
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
3538
#[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.
39+
pub(super) struct Drain<'l, 'f, T, F> {
40+
// FIXME(const-hack): This is a slice::IterMut<'l>, replace when possible.
3841
/// The pointer to the next element to return, or the past-the-end location
3942
/// if the drainer is empty.
4043
///
4144
/// This address will be used for all ZST elements, never changed.
4245
/// As we "own" this array, we dont need to store any lifetime.
4346
ptr: NonNull<T>,
4447
/// For non-ZSTs, the non-null pointer to the past-the-end element.
45-
/// For ZSTs, this is null.
46-
end: *mut T,
48+
/// For ZSTs, this is the number of unprocessed items.
49+
end_or_len: *mut T,
4750

4851
f: &'f mut F,
49-
l: PhantomData<&'l mut [T; N]>,
52+
l: PhantomData<&'l mut [T]>,
5053
}
5154

5255
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
5356
#[unstable(feature = "array_try_map", issue = "79711")]
54-
impl<T, U, const N: usize, F> const FnOnce<(usize,)> for &mut Drain<'_, '_, T, N, F>
57+
impl<T, U, F> const FnOnce<(usize,)> for &mut Drain<'_, '_, T, F>
5558
where
5659
F: [const] FnMut(T) -> U,
5760
{
@@ -64,7 +67,7 @@ where
6467
}
6568
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
6669
#[unstable(feature = "array_try_map", issue = "79711")]
67-
impl<T, U, const N: usize, F> const FnMut<(usize,)> for &mut Drain<'_, '_, T, N, F>
70+
impl<T, U, F> const FnMut<(usize,)> for &mut Drain<'_, '_, T, F>
6871
where
6972
F: [const] FnMut(T) -> U,
7073
{
@@ -74,6 +77,16 @@ where
7477
(_ /* ignore argument */,): (usize,),
7578
) -> Self::Output {
7679
if T::IS_ZST {
80+
#[expect(ptr_to_integer_transmute_in_consts)]
81+
// SAFETY:
82+
// This is equivalent to `self.end_or_len.addr`, but that's not
83+
// available in `const`. `self.end_or_len` doesn't have provenance,
84+
// so transmuting is fine.
85+
let len = unsafe { transmute::<*mut T, usize>(self.end_or_len) };
86+
// SAFETY:
87+
// The caller guarantees that this is never called more than N times
88+
// (see `Drain::new`), hence this cannot underflow.
89+
self.end_or_len = without_provenance_mut(unsafe { len.unchecked_sub(1) });
7790
// its UB to call this more than N times, so returning more ZSTs is valid.
7891
// SAFETY: its a ZST? we conjur.
7992
(self.f)(unsafe { conjure_zst::<T>() })
@@ -89,20 +102,32 @@ where
89102
}
90103
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
91104
#[unstable(feature = "array_try_map", issue = "79711")]
92-
impl<T: [const] Destruct, const N: usize, F> const Drop for Drain<'_, '_, T, N, F> {
105+
impl<T: [const] Destruct, F> const Drop for Drain<'_, '_, T, F> {
93106
fn drop(&mut self) {
94-
if !T::IS_ZST {
107+
let slice = if T::IS_ZST {
108+
from_raw_parts_mut::<[T]>(
109+
self.ptr.as_ptr(),
110+
#[expect(ptr_to_integer_transmute_in_consts)]
111+
// SAFETY:
112+
// This is equivalent to `self.end_or_len.addr`, but that's not
113+
// available in `const`. `self.end_or_len` doesn't have provenance,
114+
// so transmuting is fine.
115+
unsafe {
116+
transmute::<*mut T, usize>(self.end_or_len)
117+
},
118+
)
119+
} else {
95120
// SAFETY: we cant read more than N elements
96-
let slice = unsafe {
121+
unsafe {
97122
from_raw_parts_mut::<[T]>(
98123
self.ptr.as_ptr(),
99124
// SAFETY: `start <= end`
100-
self.end.offset_from_unsigned(self.ptr.as_ptr()),
125+
self.end_or_len.offset_from_unsigned(self.ptr.as_ptr()),
101126
)
102-
};
127+
}
128+
};
103129

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-
}
130+
// SAFETY: By the type invariant, we're allowed to drop all these. (we own it, after all)
131+
unsafe { drop_in_place(slice) }
107132
}
108133
}

library/coretests/tests/array.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use core::cell::Cell;
12
use core::num::NonZero;
23
use core::sync::atomic::{AtomicUsize, Ordering};
34
use core::{array, assert_eq};
5+
use std::sync::ReentrantLock;
46

57
#[test]
68
fn array_from_ref() {
@@ -718,6 +720,31 @@ fn array_map_drops_unmapped_elements_on_panic() {
718720
}
719721
}
720722

723+
#[cfg(not(panic = "abort"))]
724+
#[test]
725+
fn array_map_drops_unmapped_zst_elements_on_panic() {
726+
static DROPPED: ReentrantLock<Cell<usize>> = ReentrantLock::new(Cell::new(0));
727+
728+
struct ZstDrop;
729+
impl Drop for ZstDrop {
730+
fn drop(&mut self) {
731+
DROPPED.lock().update(|x| x + 1);
732+
}
733+
}
734+
735+
let dropped = DROPPED.lock();
736+
dropped.set(0);
737+
let array = [const { ZstDrop }; 5];
738+
let success = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
739+
let _ = array.map(|x| {
740+
drop(x);
741+
assert_eq!(dropped.get(), 1);
742+
});
743+
}));
744+
assert!(success.is_err());
745+
assert_eq!(dropped.get(), 5);
746+
}
747+
721748
// This covers the `PartialEq::<[T]>::eq` impl for `[T; N]` when it returns false.
722749
#[test]
723750
fn array_eq() {

library/coretests/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
#![feature(pointer_is_aligned_to)]
100100
#![feature(portable_simd)]
101101
#![feature(ptr_metadata)]
102+
#![feature(reentrant_lock)]
102103
#![feature(result_option_map_or_default)]
103104
#![feature(signed_bigint_helpers)]
104105
#![feature(slice_from_ptr_range)]

0 commit comments

Comments
 (0)