Skip to content

Commit bb89479

Browse files
authored
Rollup merge of rust-lang#82098 - LukasKalbertodt:add-collect-into-array, r=dtolnay
Add internal `collect_into_array[_unchecked]` to remove duplicate code Unlike the similar PRs rust-lang#69985, rust-lang#75644 and rust-lang#79659, this PR only adds private functions and does not propose any new public API. The change is just for the purpose of avoiding duplicate code. Many array methods already contained the same kind of code and there are still many array related methods to come (e.g. `Iterator::{chunks, map_windows, next_n, ...}`, `[T; N]::{cloned, copied, ...}`, ...) which all basically need this functionality. Writing custom `unsafe` code for each of those doesn't seem like a good idea. I added two functions in this PR (and not just the `unsafe` version) because I already know that I need the `Option`-returning version for `Iterator::map_windows`. This is closely related to rust-lang#81615. I think that all options listed in that issue can be implemented using the function added in this PR. The only instance where `collect_array_into` might not be general enough is when the caller want to handle incomplete arrays manually. Currently, if `iter` yields fewer than `N` items, `None` is returned and the already yielded items are dropped. But as this is just a private function, it can be made more general in future PRs. And while this was not the goal, this seems to lead to better assembly for `array::map`: https://rust.godbolt.org/z/75qKTa (CC `@JulianKnodt)` Let me know what you think :) CC `@matklad` `@bstrie`
2 parents 06e04fd + c675af8 commit bb89479

File tree

1 file changed

+110
-60
lines changed

1 file changed

+110
-60
lines changed

library/core/src/array/mod.rs

+110-60
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use crate::cmp::Ordering;
1111
use crate::convert::{Infallible, TryFrom};
1212
use crate::fmt;
1313
use crate::hash::{self, Hash};
14+
use crate::iter::TrustedLen;
1415
use crate::marker::Unsize;
15-
use crate::mem::MaybeUninit;
16+
use crate::mem::{self, MaybeUninit};
1617
use crate::ops::{Index, IndexMut};
1718
use crate::slice::{Iter, IterMut};
1819

@@ -426,41 +427,13 @@ impl<T, const N: usize> [T; N] {
426427
/// assert_eq!(y, [6, 9, 3, 3]);
427428
/// ```
428429
#[unstable(feature = "array_map", issue = "75243")]
429-
pub fn map<F, U>(self, mut f: F) -> [U; N]
430+
pub fn map<F, U>(self, f: F) -> [U; N]
430431
where
431432
F: FnMut(T) -> U,
432433
{
433-
struct Guard<T, const N: usize> {
434-
dst: *mut T,
435-
initialized: usize,
436-
}
437-
438-
impl<T, const N: usize> Drop for Guard<T, N> {
439-
fn drop(&mut self) {
440-
debug_assert!(self.initialized <= N);
441-
442-
let initialized_part =
443-
crate::ptr::slice_from_raw_parts_mut(self.dst, self.initialized);
444-
// SAFETY: this raw slice will contain only initialized objects
445-
// that's why, it is allowed to drop it.
446-
unsafe {
447-
crate::ptr::drop_in_place(initialized_part);
448-
}
449-
}
450-
}
451-
let mut dst = MaybeUninit::uninit_array::<N>();
452-
let mut guard: Guard<U, N> =
453-
Guard { dst: MaybeUninit::slice_as_mut_ptr(&mut dst), initialized: 0 };
454-
for (src, dst) in IntoIter::new(self).zip(&mut dst) {
455-
dst.write(f(src));
456-
guard.initialized += 1;
457-
}
458-
// FIXME: Convert to crate::mem::transmute once it works with generics.
459-
// unsafe { crate::mem::transmute::<[MaybeUninit<U>; N], [U; N]>(dst) }
460-
crate::mem::forget(guard);
461-
// SAFETY: At this point we've properly initialized the whole array
462-
// and we just need to cast it to the correct type.
463-
unsafe { crate::mem::transmute_copy::<_, [U; N]>(&dst) }
434+
// SAFETY: we know for certain that this iterator will yield exactly `N`
435+
// items.
436+
unsafe { collect_into_array_unchecked(&mut IntoIter::new(self).map(f)) }
464437
}
465438

466439
/// 'Zips up' two arrays into a single array of pairs.
@@ -481,15 +454,11 @@ impl<T, const N: usize> [T; N] {
481454
/// ```
482455
#[unstable(feature = "array_zip", issue = "80094")]
483456
pub fn zip<U>(self, rhs: [U; N]) -> [(T, U); N] {
484-
let mut dst = MaybeUninit::uninit_array::<N>();
485-
for (i, (lhs, rhs)) in IntoIter::new(self).zip(IntoIter::new(rhs)).enumerate() {
486-
dst[i].write((lhs, rhs));
487-
}
488-
// FIXME: Convert to crate::mem::transmute once it works with generics.
489-
// unsafe { crate::mem::transmute::<[MaybeUninit<U>; N], [U; N]>(dst) }
490-
// SAFETY: At this point we've properly initialized the whole array
491-
// and we just need to cast it to the correct type.
492-
unsafe { crate::mem::transmute_copy::<_, [(T, U); N]>(&dst) }
457+
let mut iter = IntoIter::new(self).zip(IntoIter::new(rhs));
458+
459+
// SAFETY: we know for certain that this iterator will yield exactly `N`
460+
// items.
461+
unsafe { collect_into_array_unchecked(&mut iter) }
493462
}
494463

495464
/// Returns a slice containing the entire array. Equivalent to `&s[..]`.
@@ -535,16 +504,9 @@ impl<T, const N: usize> [T; N] {
535504
/// ```
536505
#[unstable(feature = "array_methods", issue = "76118")]
537506
pub fn each_ref(&self) -> [&T; N] {
538-
// Unlike in `map`, we don't need a guard here, as dropping a reference
539-
// is a noop.
540-
let mut out = MaybeUninit::uninit_array::<N>();
541-
for (src, dst) in self.iter().zip(&mut out) {
542-
dst.write(src);
543-
}
544-
545-
// SAFETY: All elements of `dst` are properly initialized and
546-
// `MaybeUninit<T>` has the same layout as `T`, so this cast is valid.
547-
unsafe { (&mut out as *mut _ as *mut [&T; N]).read() }
507+
// SAFETY: we know for certain that this iterator will yield exactly `N`
508+
// items.
509+
unsafe { collect_into_array_unchecked(&mut self.iter()) }
548510
}
549511

550512
/// Borrows each element mutably and returns an array of mutable references
@@ -564,15 +526,103 @@ impl<T, const N: usize> [T; N] {
564526
/// ```
565527
#[unstable(feature = "array_methods", issue = "76118")]
566528
pub fn each_mut(&mut self) -> [&mut T; N] {
567-
// Unlike in `map`, we don't need a guard here, as dropping a reference
568-
// is a noop.
569-
let mut out = MaybeUninit::uninit_array::<N>();
570-
for (src, dst) in self.iter_mut().zip(&mut out) {
571-
dst.write(src);
529+
// SAFETY: we know for certain that this iterator will yield exactly `N`
530+
// items.
531+
unsafe { collect_into_array_unchecked(&mut self.iter_mut()) }
532+
}
533+
}
534+
535+
/// Pulls `N` items from `iter` and returns them as an array. If the iterator
536+
/// yields fewer than `N` items, this function exhibits undefined behavior.
537+
///
538+
/// See [`collect_into_array`] for more information.
539+
///
540+
///
541+
/// # Safety
542+
///
543+
/// It is up to the caller to guarantee that `iter` yields at least `N` items.
544+
/// Violating this condition causes undefined behavior.
545+
unsafe fn collect_into_array_unchecked<I, const N: usize>(iter: &mut I) -> [I::Item; N]
546+
where
547+
// Note: `TrustedLen` here is somewhat of an experiment. This is just an
548+
// internal function, so feel free to remove if this bound turns out to be a
549+
// bad idea. In that case, remember to also remove the lower bound
550+
// `debug_assert!` below!
551+
I: Iterator + TrustedLen,
552+
{
553+
debug_assert!(N <= iter.size_hint().1.unwrap_or(usize::MAX));
554+
debug_assert!(N <= iter.size_hint().0);
555+
556+
match collect_into_array(iter) {
557+
Some(array) => array,
558+
// SAFETY: covered by the function contract.
559+
None => unsafe { crate::hint::unreachable_unchecked() },
560+
}
561+
}
562+
563+
/// Pulls `N` items from `iter` and returns them as an array. If the iterator
564+
/// yields fewer than `N` items, `None` is returned and all already yielded
565+
/// items are dropped.
566+
///
567+
/// Since the iterator is passed as mutable reference and this function calls
568+
/// `next` at most `N` times, the iterator can still be used afterwards to
569+
/// retrieve the remaining items.
570+
///
571+
/// If `iter.next()` panicks, all items already yielded by the iterator are
572+
/// dropped.
573+
fn collect_into_array<I, const N: usize>(iter: &mut I) -> Option<[I::Item; N]>
574+
where
575+
I: Iterator,
576+
{
577+
if N == 0 {
578+
// SAFETY: An empty array is always inhabited and has no validity invariants.
579+
return unsafe { Some(mem::zeroed()) };
580+
}
581+
582+
struct Guard<T, const N: usize> {
583+
ptr: *mut T,
584+
initialized: usize,
585+
}
586+
587+
impl<T, const N: usize> Drop for Guard<T, N> {
588+
fn drop(&mut self) {
589+
debug_assert!(self.initialized <= N);
590+
591+
let initialized_part = crate::ptr::slice_from_raw_parts_mut(self.ptr, self.initialized);
592+
593+
// SAFETY: this raw slice will contain only initialized objects.
594+
unsafe {
595+
crate::ptr::drop_in_place(initialized_part);
596+
}
597+
}
598+
}
599+
600+
let mut array = MaybeUninit::uninit_array::<N>();
601+
let mut guard: Guard<_, N> =
602+
Guard { ptr: MaybeUninit::slice_as_mut_ptr(&mut array), initialized: 0 };
603+
604+
while let Some(item) = iter.next() {
605+
// SAFETY: `guard.initialized` starts at 0, is increased by one in the
606+
// loop and the loop is aborted once it reaches N (which is
607+
// `array.len()`).
608+
unsafe {
609+
array.get_unchecked_mut(guard.initialized).write(item);
572610
}
611+
guard.initialized += 1;
612+
613+
// Check if the whole array was initialized.
614+
if guard.initialized == N {
615+
mem::forget(guard);
573616

574-
// SAFETY: All elements of `dst` are properly initialized and
575-
// `MaybeUninit<T>` has the same layout as `T`, so this cast is valid.
576-
unsafe { (&mut out as *mut _ as *mut [&mut T; N]).read() }
617+
// SAFETY: the condition above asserts that all elements are
618+
// initialized.
619+
let out = unsafe { MaybeUninit::array_assume_init(array) };
620+
return Some(out);
621+
}
577622
}
623+
624+
// This is only reached if the iterator is exhausted before
625+
// `guard.initialized` reaches `N`. Also note that `guard` is dropped here,
626+
// dropping all already initialized elements.
627+
None
578628
}

0 commit comments

Comments
 (0)