#![doc(html_root_url = "https://docs.rs/array_try_map/0.1.0")]
#![no_std]
#![feature(
min_const_generics,
maybe_uninit_uninit_array,
maybe_uninit_extra,
maybe_uninit_slice,
array_value_iter,
never_type,
unwrap_infallible
)]
#![deny(missing_docs)]
pub trait ArrayExt<T, const N: usize> {
fn try_map<F, U, E>(self, f: F) -> Result<[U; N], E>
where
F: FnMut(T) -> Result<U, E>;
fn map2<F, U>(self, f: F) -> [U; N]
where
F: FnMut(T) -> U;
}
impl<T, const N: usize> ArrayExt<T, N> for [T; N] {
fn try_map<F, U, E>(self, mut f: F) -> Result<[U; N], E>
where
F: FnMut(T) -> Result<U, E>,
{
use core::mem::MaybeUninit;
struct Guard<T, const N: usize> {
dst: *mut T,
initialized: usize,
}
impl<T, const N: usize> Drop for Guard<T, N> {
fn drop(&mut self) {
debug_assert!(self.initialized <= N);
let initialized_part =
core::ptr::slice_from_raw_parts_mut(self.dst, self.initialized);
unsafe {
core::ptr::drop_in_place(initialized_part);
}
}
}
let mut dst = MaybeUninit::uninit_array::<N>();
let mut guard: Guard<U, N> = Guard {
dst: MaybeUninit::slice_as_mut_ptr(&mut dst),
initialized: 0,
};
for (src, dst) in core::array::IntoIter::new(self).zip(&mut dst) {
match f(src) {
Ok(elem) => {
dst.write(elem);
guard.initialized += 1;
}
Err(err) => return Err(err),
}
}
core::mem::forget(guard);
Ok(unsafe { core::mem::transmute_copy::<_, [U; N]>(&dst) }) }
fn map2<F, U>(self, mut f: F) -> [U; N]
where
F: FnMut(T) -> U,
{
self.try_map::<_, _, !>(|src| Ok(f(src))).into_ok()
}
}
#[cfg(test)]
mod test {
extern crate std;
use super::ArrayExt;
use std::{
mem, panic,
rc::Rc,
sync::atomic::{AtomicUsize, Ordering},
};
#[test]
fn drop_on_err() {
let x = [0, 0, 0, 0, 255];
let rc = Rc::new(());
let _ = x.try_map(|i| if i == 0 { Ok(rc.clone()) } else { Err(()) });
assert_eq!(Rc::strong_count(&rc), 1);
}
#[test]
fn drop_on_panic() {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
struct ObjectCounter;
impl ObjectCounter {
fn new() -> Self {
COUNTER.fetch_add(1, Ordering::AcqRel);
Self
}
}
impl Drop for ObjectCounter {
fn drop(&mut self) {
COUNTER.fetch_sub(1, Ordering::AcqRel);
}
}
let f = |i| {
if i == 0 {
ObjectCounter::new()
} else {
panic!("expected panic")
}
};
let x = [0, 0, 0, 0];
let res = panic::catch_unwind(move || x.map2(f));
assert_eq!(COUNTER.load(Ordering::Acquire), 4);
mem::drop(res);
let x = [0, 0, 0, 0, 255];
let _res = panic::catch_unwind(move || x.map2(f));
assert_eq!(COUNTER.load(Ordering::Acquire), 0);
}
#[test]
fn short_circuit() {
let mut counter = 0;
let x = [0, 0, 0, 0];
let _ = x.try_map(|i| {
if i == 0 {
counter += 1;
Ok(())
} else {
Err(())
}
});
assert_eq!(counter, 4);
counter = 0;
let y = [0, 0, 255, 0, 0];
let _ = y.try_map(|i| {
if i == 0 {
counter += 1;
Ok(())
} else {
Err(())
}
});
assert_eq!(counter, 2);
}
}