Skip to content

Commit 4be574e

Browse files
committed
Add 'core::array::from_fn' and 'core::array::try_from_fn'
1 parent ac8dd1b commit 4be574e

File tree

3 files changed

+171
-9
lines changed

3 files changed

+171
-9
lines changed

library/core/src/array/mod.rs

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,69 @@ mod iter;
2020
#[stable(feature = "array_value_iter", since = "1.51.0")]
2121
pub use iter::IntoIter;
2222

23+
/// Creates an array `[T; N]` where each array element `T` is returned by the `cb` call.
24+
///
25+
/// # Arguments
26+
///
27+
/// * `cb`: Callback where the passed argument is the current array index.
28+
///
29+
/// # Example
30+
///
31+
/// ```rust
32+
/// #![feature(array_from_fn)]
33+
///
34+
/// let array = core::array::from_fn(|i| i);
35+
/// assert_eq!(array, [0, 1, 2, 3, 4]);
36+
/// ```
37+
#[inline]
38+
#[unstable(feature = "array_from_fn", issue = "89379")]
39+
pub fn from_fn<F, T, const N: usize>(mut cb: F) -> [T; N]
40+
where
41+
F: FnMut(usize) -> T,
42+
{
43+
let mut idx = 0;
44+
[(); N].map(|_| {
45+
let res = cb(idx);
46+
idx += 1;
47+
res
48+
})
49+
}
50+
51+
/// Creates an array `[T; N]` where each fallible array element `T` is returned by the `cb` call.
52+
/// Unlike `core::array::from_fn`, where the element creation can't fail, this version will return an error
53+
/// if any element creation was unsuccessful.
54+
///
55+
/// # Arguments
56+
///
57+
/// * `cb`: Callback where the passed argument is the current array index.
58+
///
59+
/// # Example
60+
///
61+
/// ```rust
62+
/// #![feature(array_from_fn)]
63+
///
64+
/// #[derive(Debug, PartialEq)]
65+
/// enum SomeError {
66+
/// Foo,
67+
/// }
68+
///
69+
/// let array = core::array::try_from_fn(|i| Ok::<_, SomeError>(i));
70+
/// assert_eq!(array, Ok([0, 1, 2, 3, 4]));
71+
///
72+
/// let another_array = core::array::try_from_fn::<SomeError, _, (), 2>(|_| Err(SomeError::Foo));
73+
/// assert_eq!(another_array, Err(SomeError::Foo));
74+
/// ```
75+
#[inline]
76+
#[unstable(feature = "array_from_fn", issue = "89379")]
77+
pub fn try_from_fn<E, F, T, const N: usize>(cb: F) -> Result<[T; N], E>
78+
where
79+
F: FnMut(usize) -> Result<T, E>,
80+
{
81+
// SAFETY: we know for certain that this iterator will yield exactly `N`
82+
// items.
83+
unsafe { collect_into_array_rslt_unchecked(&mut (0..N).map(cb)) }
84+
}
85+
2386
/// Converts a reference to `T` into a reference to an array of length 1 (without copying).
2487
#[stable(feature = "array_from_ref", since = "1.53.0")]
2588
pub fn from_ref<T>(s: &T) -> &[T; 1] {
@@ -448,13 +511,15 @@ impl<T, const N: usize> [T; N] {
448511
///
449512
/// It is up to the caller to guarantee that `iter` yields at least `N` items.
450513
/// Violating this condition causes undefined behavior.
451-
unsafe fn collect_into_array_unchecked<I, const N: usize>(iter: &mut I) -> [I::Item; N]
514+
unsafe fn collect_into_array_rslt_unchecked<E, I, T, const N: usize>(
515+
iter: &mut I,
516+
) -> Result<[T; N], E>
452517
where
453518
// Note: `TrustedLen` here is somewhat of an experiment. This is just an
454519
// internal function, so feel free to remove if this bound turns out to be a
455520
// bad idea. In that case, remember to also remove the lower bound
456521
// `debug_assert!` below!
457-
I: Iterator + TrustedLen,
522+
I: Iterator<Item = Result<T, E>> + TrustedLen,
458523
{
459524
debug_assert!(N <= iter.size_hint().1.unwrap_or(usize::MAX));
460525
debug_assert!(N <= iter.size_hint().0);
@@ -463,6 +528,18 @@ where
463528
unsafe { collect_into_array(iter).unwrap_unchecked() }
464529
}
465530

531+
// Infallible version of `collect_into_array_rslt_unchecked`.
532+
unsafe fn collect_into_array_unchecked<I, const N: usize>(iter: &mut I) -> [I::Item; N]
533+
where
534+
I: Iterator + TrustedLen,
535+
{
536+
let mut map = iter.map(|el| Ok::<_, Infallible>(el));
537+
538+
// SAFETY: Valid array elements are covered by the fact that all passed values
539+
// to `collect_into_array` are `Ok`.
540+
unsafe { collect_into_array_rslt_unchecked(&mut map).unwrap_unchecked() }
541+
}
542+
466543
/// Pulls `N` items from `iter` and returns them as an array. If the iterator
467544
/// yields fewer than `N` items, `None` is returned and all already yielded
468545
/// items are dropped.
@@ -473,13 +550,13 @@ where
473550
///
474551
/// If `iter.next()` panicks, all items already yielded by the iterator are
475552
/// dropped.
476-
fn collect_into_array<I, const N: usize>(iter: &mut I) -> Option<[I::Item; N]>
553+
fn collect_into_array<E, I, T, const N: usize>(iter: &mut I) -> Option<Result<[T; N], E>>
477554
where
478-
I: Iterator,
555+
I: Iterator<Item = Result<T, E>>,
479556
{
480557
if N == 0 {
481558
// SAFETY: An empty array is always inhabited and has no validity invariants.
482-
return unsafe { Some(mem::zeroed()) };
559+
return unsafe { Some(Ok(mem::zeroed())) };
483560
}
484561

485562
struct Guard<T, const N: usize> {
@@ -504,7 +581,14 @@ where
504581
let mut guard: Guard<_, N> =
505582
Guard { ptr: MaybeUninit::slice_as_mut_ptr(&mut array), initialized: 0 };
506583

507-
while let Some(item) = iter.next() {
584+
while let Some(item_rslt) = iter.next() {
585+
let item = match item_rslt {
586+
Err(err) => {
587+
return Some(Err(err));
588+
}
589+
Ok(elem) => elem,
590+
};
591+
508592
// SAFETY: `guard.initialized` starts at 0, is increased by one in the
509593
// loop and the loop is aborted once it reaches N (which is
510594
// `array.len()`).
@@ -520,7 +604,7 @@ where
520604
// SAFETY: the condition above asserts that all elements are
521605
// initialized.
522606
let out = unsafe { MaybeUninit::array_assume_init(array) };
523-
return Some(out);
607+
return Some(Ok(out));
524608
}
525609
}
526610

library/core/tests/array.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use core::array;
22
use core::convert::TryFrom;
3+
use core::sync::atomic::{AtomicUsize, Ordering};
34

45
#[test]
56
fn array_from_ref() {
@@ -303,8 +304,6 @@ fn array_map() {
303304
#[test]
304305
#[should_panic(expected = "test succeeded")]
305306
fn array_map_drop_safety() {
306-
use core::sync::atomic::AtomicUsize;
307-
use core::sync::atomic::Ordering;
308307
static DROPPED: AtomicUsize = AtomicUsize::new(0);
309308
struct DropCounter;
310309
impl Drop for DropCounter {
@@ -356,3 +355,81 @@ fn cell_allows_array_cycle() {
356355
b3.a[0].set(Some(&b1));
357356
b3.a[1].set(Some(&b2));
358357
}
358+
359+
#[test]
360+
fn array_from_fn() {
361+
let array = core::array::from_fn(|idx| idx);
362+
assert_eq!(array, [0, 1, 2, 3, 4]);
363+
}
364+
365+
#[test]
366+
fn array_try_from_fn() {
367+
#[derive(Debug, PartialEq)]
368+
enum SomeError {
369+
Foo,
370+
}
371+
372+
let array = core::array::try_from_fn(|i| Ok::<_, SomeError>(i));
373+
assert_eq!(array, Ok([0, 1, 2, 3, 4]));
374+
375+
let another_array = core::array::try_from_fn::<SomeError, _, (), 2>(|_| Err(SomeError::Foo));
376+
assert_eq!(another_array, Err(SomeError::Foo));
377+
}
378+
379+
#[test]
380+
fn array_try_from_fn_drops_inserted_elements_on_err() {
381+
static DROP_COUNTER: AtomicUsize = AtomicUsize::new(0);
382+
383+
struct CountDrop;
384+
impl Drop for CountDrop {
385+
fn drop(&mut self) {
386+
DROP_COUNTER.fetch_add(1, Ordering::SeqCst);
387+
}
388+
}
389+
390+
let _ = catch_unwind_silent(move || {
391+
let _: Result<[CountDrop; 4], ()> = core::array::try_from_fn(|idx| {
392+
if idx == 2 {
393+
return Err(());
394+
}
395+
Ok(CountDrop)
396+
});
397+
});
398+
399+
assert_eq!(DROP_COUNTER.load(Ordering::SeqCst), 2);
400+
}
401+
402+
#[test]
403+
fn array_try_from_fn_drops_inserted_elements_on_panic() {
404+
static DROP_COUNTER: AtomicUsize = AtomicUsize::new(0);
405+
406+
struct CountDrop;
407+
impl Drop for CountDrop {
408+
fn drop(&mut self) {
409+
DROP_COUNTER.fetch_add(1, Ordering::SeqCst);
410+
}
411+
}
412+
413+
let _ = catch_unwind_silent(move || {
414+
let _: Result<[CountDrop; 4], ()> = core::array::try_from_fn(|idx| {
415+
if idx == 2 {
416+
panic!("peek a boo");
417+
}
418+
Ok(CountDrop)
419+
});
420+
});
421+
422+
assert_eq!(DROP_COUNTER.load(Ordering::SeqCst), 2);
423+
}
424+
425+
// https://stackoverflow.com/a/59211505
426+
fn catch_unwind_silent<F, R>(f: F) -> std::thread::Result<R>
427+
where
428+
F: FnOnce() -> R + core::panic::UnwindSafe,
429+
{
430+
let prev_hook = std::panic::take_hook();
431+
std::panic::set_hook(Box::new(|_| {}));
432+
let result = std::panic::catch_unwind(f);
433+
std::panic::set_hook(prev_hook);
434+
result
435+
}

library/core/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#![feature(extern_types)]
2727
#![feature(flt2dec)]
2828
#![feature(fmt_internals)]
29+
#![feature(array_from_fn)]
2930
#![feature(hashmap_internals)]
3031
#![feature(try_find)]
3132
#![feature(is_sorted)]

0 commit comments

Comments
 (0)