diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
commit | 9835e2ae736235810b4ea1c162ca5e65c547e770 (patch) | |
tree | 3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/unarray/src/map.rs | |
parent | Releasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff) | |
download | rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/unarray/src/map.rs')
-rw-r--r-- | vendor/unarray/src/map.rs | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/vendor/unarray/src/map.rs b/vendor/unarray/src/map.rs new file mode 100644 index 000000000..3b7e74574 --- /dev/null +++ b/vendor/unarray/src/map.rs @@ -0,0 +1,207 @@ +use crate::{mark_initialized, uninit_buf}; + +/// An extension trait that adds methods to `[T; N]` +/// +/// This trait provides [`UnarrayArrayExt::map_result`] and [`UnarrayArrayExt::map_option`], +/// which provide functionality similar to the nightly-only [`array::try_map`] +pub trait UnarrayArrayExt<T, const N: usize> { + /// Maps an array, short-circuiting if any element produces an `Err` + /// + /// ``` + /// # use unarray::*; + /// let elements = ["123", "234", "345"]; + /// let mapped = elements.map_result(|s| s.parse()); + /// assert_eq!(mapped, Ok([123, 234, 345])); + /// ``` + /// + /// This function applies `f` to every element. If any element produces an `Err`, the function + /// immediately returns that error. Otherwise, it returns `Ok(result)` where `result` contains + /// the mapped elements in an array. + /// + /// This function does not allocate space on the heap + /// + /// For functions that return an `Option`, consider using [`UnarrayArrayExt::map_option`] + fn map_result<S, E>(self, f: impl FnMut(T) -> Result<S, E>) -> Result<[S; N], E>; + + /// Maps an array, short-circuiting if any element produces a `None` + /// + /// ``` + /// # use unarray::*; + /// fn parse(s: &str) -> Option<bool> { + /// match s { + /// "true" => Some(true), + /// "false" => Some(false), + /// _ => None, + /// } + /// } + /// + /// let elements = ["true", "false", "true"]; + /// let mapped = elements.map_option(parse); + /// assert_eq!(mapped, Some([true, false, true])); + /// ``` + /// + /// This function applies `f` to every element. If any element produces `None`, the function + /// immediately returns `None`. Otherwise, it returns `Some(result)` where `result` contains + /// the mapped elements in an array. + /// + /// This function does not allocate space on the heap + /// + /// For functions that return an `Result`, consider using [`UnarrayArrayExt::map_result`] + fn map_option<S>(self, f: impl FnMut(T) -> Option<S>) -> Option<[S; N]>; +} + +impl<T, const N: usize> UnarrayArrayExt<T, N> for [T; N] { + fn map_result<S, E>(self, mut f: impl FnMut(T) -> Result<S, E>) -> Result<[S; N], E> { + let mut result = uninit_buf(); + + // This is quaranteed to loop over every element (or panic), since both `result` and `self` have N elements + // If a panic occurs, uninitialized data is never dropped, since `MaybeUninit` wraps its + // contained data in `ManuallyDrop` + for (index, (item, slot)) in IntoIterator::into_iter(self).zip(&mut result).enumerate() { + match f(item) { + Ok(s) => slot.write(s), + Err(e) => { + // SAFETY: + // We have failed at `index` which is the `index + 1`th element, so the first + // `index` elements are safe to drop + result + .iter_mut() + .take(index) + .for_each(|slot| unsafe { slot.assume_init_drop() }); + return Err(e); + } + }; + } + + // SAFETY: + // At this point in execution, we have iterated over all elements of `result`. If any + // errors were encountered, we would have already returned. So it's safe to remove the + // MaybeUninit wrapper + Ok(unsafe { mark_initialized(result) }) + } + + fn map_option<S>(self, mut f: impl FnMut(T) -> Option<S>) -> Option<[S; N]> { + // transform to a `Result`-returning function so we can avoid duplicating unsafe code + let actual_f = |t: T| -> Result<S, ()> { f(t).ok_or(()) }; + + let result: Result<[S; N], ()> = UnarrayArrayExt::map_result(self, actual_f); + match result { + Ok(result) => Some(result), + Err(()) => None, + } + } +} + +#[cfg(test)] +mod tests { + use core::{ + convert::TryInto, + sync::atomic::{AtomicUsize, Ordering}, + }; + + use super::UnarrayArrayExt; + use crate::testing::array_strategy; + use proptest::prelude::*; + use test_strategy::proptest; + + #[test] + fn test_map_option() { + let array = [1, 2, 3]; + let result = array.map_option(|i| Some(i * 2)).unwrap(); + assert_eq!(result, [2, 4, 6]); + } + + #[test] + #[should_panic] + fn test_map_option_panic() { + let array = [1, 2, 3]; + array.map_option(|i| { + if i > 2 { + panic!(); + } + + Some(i) + }); + } + + #[test] + fn test_map_result() { + let array = [1, 2, 3]; + let result: Result<_, ()> = array.map_result(|i| Ok(i * 2)); + assert_eq!(result.unwrap(), [2, 4, 6]); + } + + #[test] + #[should_panic] + fn test_map_result_panic() { + let array = [1, 2, 3]; + let _ = array.map_result(|i| -> Result<i32, ()> { + if i > 2 { + panic!(); + } + + Ok(i) + }); + } + + struct IncrementOnDrop<'a>(&'a AtomicUsize); + impl Drop for IncrementOnDrop<'_> { + fn drop(&mut self) { + self.0.fetch_add(1, Ordering::Relaxed); + } + } + + #[test] + fn map_array_result_doesnt_leak() { + let drop_counter = 0.into(); + + // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be + // called, since the 4th may be in an inconsistent state + let _ = [0, 1, 2, 3, 4].map_result(|i| { + if i == 3 { + Err(()) + } else { + Ok(IncrementOnDrop(&drop_counter)) + } + }); + + assert_eq!(drop_counter.load(Ordering::Relaxed), 3); + } + + #[test] + fn map_array_option_doesnt_leak() { + let drop_counter = 0.into(); + + // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be + // called, since the 4th may be in an inconsistent state + let _ = [0, 1, 2, 3, 4].map_option(|i| { + if i == 3 { + None + } else { + Some(IncrementOnDrop(&drop_counter)) + } + }); + + assert_eq!(drop_counter.load(Ordering::Relaxed), 3); + } + + const LEN: usize = 100; + + #[proptest] + #[cfg_attr(miri, ignore)] + fn proptest_option_map(#[strategy(array_strategy::<LEN>())] array: [String; LEN]) { + let expected = array.iter().map(|s| s.len()).collect::<Vec<_>>(); + let expected: [usize; LEN] = expected.try_into().unwrap(); + let result = array.map_option(|s| Some(s.len())); + prop_assert_eq!(expected, result.unwrap()); + } + + #[proptest] + #[cfg_attr(miri, ignore)] + fn proptest_result_map(#[strategy(array_strategy::<LEN>())] array: [String; LEN]) { + let expected = array.iter().map(|s| s.len()).collect::<Vec<_>>(); + let expected: [usize; LEN] = expected.try_into().unwrap(); + let result: Result<_, ()> = array.map_result(|s| Ok(s.len())); + prop_assert_eq!(expected, result.unwrap()); + } +} |