summaryrefslogtreecommitdiffstats
path: root/vendor/unarray/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/unarray/src')
-rw-r--r--vendor/unarray/src/build.rs167
-rw-r--r--vendor/unarray/src/from_iter.rs148
-rw-r--r--vendor/unarray/src/lib.rs195
-rw-r--r--vendor/unarray/src/map.rs207
-rw-r--r--vendor/unarray/src/testing.rs12
5 files changed, 729 insertions, 0 deletions
diff --git a/vendor/unarray/src/build.rs b/vendor/unarray/src/build.rs
new file mode 100644
index 000000000..620b68de0
--- /dev/null
+++ b/vendor/unarray/src/build.rs
@@ -0,0 +1,167 @@
+use crate::{mark_initialized, uninit_buf};
+
+/// Build an array with a function that creates elements based on their index
+///
+/// ```
+/// # use unarray::*;
+/// let array: [usize; 5] = build_array(|i| i * 2);
+/// assert_eq!(array, [0, 2, 4, 6, 8]);
+/// ```
+/// If `f` panics, any already-initialized elements will be dropped **without** running their
+/// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe",
+/// since Rust's notion of "safety" doesn't guarantee destructors are run.
+///
+/// For builder functions which might fail, consider using [`build_array_result`] or
+/// [`build_array_option`]
+pub fn build_array<T, F: FnMut(usize) -> T, const N: usize>(mut f: F) -> [T; N] {
+ let mut result = uninit_buf();
+
+ for (index, slot) in result.iter_mut().enumerate() {
+ let value = f(index);
+ slot.write(value);
+ }
+
+ // SAFETY:
+ // We have iterated over every element in result and called `.write()` on it, so every element
+ // is initialized
+ unsafe { mark_initialized(result) }
+}
+
+/// Build an array with a function that creates elements based on their value, short-circuiting if
+/// any index returns an `Err`
+///
+/// ```
+/// # use unarray::*;
+///
+/// let success: Result<_, ()> = build_array_result(|i| Ok(i * 2));
+/// assert_eq!(success, Ok([0, 2, 4]));
+/// ```
+///
+/// If `f` panics, any already-initialized elements will be dropped **without** running their
+/// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe",
+/// since Rust's notion of "safety" doesn't guarantee destructors are run.
+///
+/// This is similar to the nightly-only [`core::array::try_from_fn`]
+pub fn build_array_result<T, E, F: FnMut(usize) -> Result<T, E>, const N: usize>(
+ mut f: F,
+) -> Result<[T; N], E> {
+ let mut result = uninit_buf();
+
+ for (index, slot) in result.iter_mut().enumerate() {
+ match f(index) {
+ Ok(value) => slot.write(value),
+ 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:
+ // We have iterated over every element in result and called `.write()` on it, so every element
+ // is initialized
+ Ok(unsafe { mark_initialized(result) })
+}
+
+/// Build an array with a function that creates elements based on their value, short-circuiting if
+/// any index returns a `None`
+///
+/// ```
+/// # use unarray::*;
+/// let success = build_array_option(|i| Some(i * 2));
+/// assert_eq!(success, Some([0, 2, 4]));
+/// ```
+///
+/// If `f` panics, any already-initialized elements will be dropped **without** running their
+/// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe",
+/// since Rust's notion of "safety" doesn't guarantee destructors are run.
+///
+/// This is similar to the nightly-only [`core::array::try_from_fn`]
+pub fn build_array_option<T, F: FnMut(usize) -> Option<T>, const N: usize>(
+ mut f: F,
+) -> Option<[T; N]> {
+ let actual_f = |i: usize| -> Result<T, ()> { f(i).ok_or(()) };
+
+ match build_array_result(actual_f) {
+ Ok(array) => Some(array),
+ Err(()) => None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use core::sync::atomic::{AtomicUsize, Ordering};
+
+ use super::*;
+
+ #[test]
+ fn test_build_array() {
+ let array = build_array(|i| i * 2);
+ assert_eq!(array, [0, 2, 4]);
+ }
+
+ #[test]
+ fn test_build_array_option() {
+ let array = build_array_option(|i| Some(i * 2));
+ assert_eq!(array, Some([0, 2, 4]));
+
+ let none: Option<[_; 10]> = build_array_option(|i| if i == 5 { None } else { Some(()) });
+ assert_eq!(none, None);
+ }
+
+ #[test]
+ fn test_build_array_result() {
+ let array = build_array_result(|i| Ok::<usize, ()>(i * 2));
+ assert_eq!(array, Ok([0, 2, 4]));
+
+ let err: Result<[_; 10], _> = build_array_result(|i| if i == 5 { Err(()) } else { Ok(()) });
+ assert_eq!(err, Err(()));
+ }
+
+ struct IncrementOnDrop<'a>(&'a AtomicUsize);
+ impl Drop for IncrementOnDrop<'_> {
+ fn drop(&mut self) {
+ self.0.fetch_add(1, Ordering::Relaxed);
+ }
+ }
+
+ #[test]
+ fn result_doesnt_leak_on_err() {
+ 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 _: Result<[_; 5], _> = build_array_result(|i| {
+ if i == 3 {
+ Err(())
+ } else {
+ Ok(IncrementOnDrop(&drop_counter))
+ }
+ });
+
+ assert_eq!(drop_counter.load(Ordering::Relaxed), 3);
+ }
+
+ #[test]
+ fn option_doesnt_leak_on_err() {
+ 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 _: Option<[_; 5]> = build_array_option(|i| {
+ if i == 3 {
+ None
+ } else {
+ Some(IncrementOnDrop(&drop_counter))
+ }
+ });
+
+ assert_eq!(drop_counter.load(Ordering::Relaxed), 3);
+ }
+}
diff --git a/vendor/unarray/src/from_iter.rs b/vendor/unarray/src/from_iter.rs
new file mode 100644
index 000000000..e96275bb2
--- /dev/null
+++ b/vendor/unarray/src/from_iter.rs
@@ -0,0 +1,148 @@
+use crate::{mark_initialized, uninit_buf};
+use core::iter::FromIterator;
+
+/// A wrapper type to collect an [`Iterator`] into an array
+///
+/// ```
+/// # use unarray::*;
+/// let iter = vec![1, 2, 3].into_iter();
+/// let ArrayFromIter(array) = iter.collect();
+///
+/// assert_eq!(array, Some([1, 2, 3]));
+/// ```
+/// Since iterators don't carry compile-time information about their length (even
+/// [`core::iter::ExactSizeIterator`] only provides this at runtime), collecting may fail if the
+/// iterator doesn't yield **exactly** `N` elements:
+/// ```
+/// use unarray::*;
+/// let too_many = vec![1, 2, 3, 4].into_iter();
+/// let ArrayFromIter::<i32, 3>(option) = too_many.collect();
+/// assert!(option.is_none());
+///
+/// let too_few = vec![1, 2].into_iter();
+/// let ArrayFromIter::<i32, 3>(option) = too_few.collect();
+/// assert!(option.is_none());
+/// ```
+pub struct ArrayFromIter<T, const N: usize>(pub Option<[T; N]>);
+
+impl<T, const N: usize> FromIterator<T> for ArrayFromIter<T, N> {
+ fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
+ let mut buffer = uninit_buf::<T, N>();
+ let mut iter = iter.into_iter();
+ let mut buf_iter = buffer.iter_mut();
+ let mut num_written = 0;
+
+ loop {
+ let item = iter.next();
+ let slot = buf_iter.next();
+
+ match (item, slot) {
+ (Some(item), Some(slot)) => {
+ num_written += 1;
+ slot.write(item);
+ }
+ // error case, we should free the previous ones and continue
+ (Some(_), None) | (None, Some(_)) => {
+ // SAFETY:
+ // There are `num_written` elements fully initialized, so we can safely drop
+ // them
+ buffer
+ .iter_mut()
+ .take(num_written)
+ .for_each(|slot| unsafe { slot.assume_init_drop() });
+
+ return Self(None);
+ }
+ // SAFETY
+ // If this is reached, every prior iteration of the loop has matched
+ // (Some(_), Some(_)). As such, both iterators have yielded the same number of
+ // elements, so every slot has been written to
+ (None, None) => return Self(Some(unsafe { mark_initialized(buffer) })),
+ };
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use core::{
+ convert::TryInto,
+ sync::atomic::{AtomicUsize, Ordering},
+ };
+
+ use crate::testing::vec_strategy;
+ use proptest::{prop_assert, prop_assert_eq};
+ use test_strategy::proptest;
+
+ use super::*;
+
+ #[test]
+ fn can_collect_array_from_iter() {
+ let iter = vec![1, 2, 3].into_iter();
+
+ let ArrayFromIter(array) = iter.collect();
+ assert_eq!(array.unwrap(), [1, 2, 3]);
+ }
+
+ #[test]
+ fn fails_if_incorrect_number_of_elements() {
+ let iter = [1, 2, 3].iter();
+ let ArrayFromIter::<_, 4>(array) = iter.collect();
+ assert!(array.is_none());
+
+ let iter = [1, 2, 3].iter();
+ let ArrayFromIter::<_, 2>(array) = iter.collect();
+ assert!(array.is_none());
+ }
+
+ const LEN: usize = 100;
+ const SHORT_LEN: usize = LEN - 1;
+ const LONG_LEN: usize = LEN + 1;
+
+ #[derive(Clone)]
+ struct IncrementOnDrop<'a>(&'a AtomicUsize);
+ impl Drop for IncrementOnDrop<'_> {
+ fn drop(&mut self) {
+ self.0.fetch_add(1, Ordering::Relaxed);
+ }
+ }
+
+ #[test]
+ fn doesnt_leak_too_long() {
+ let drop_count = 0.into();
+ let ArrayFromIter::<_, 3>(_) = vec![IncrementOnDrop(&drop_count); 4].into_iter().collect();
+ // since it failed, all 4 should be dropped
+ assert_eq!(drop_count.load(Ordering::Relaxed), 4);
+ }
+
+ #[test]
+ fn doesnt_leak_too_short() {
+ let drop_count = 0.into();
+ let ArrayFromIter::<_, 3>(_) = vec![IncrementOnDrop(&drop_count); 2].into_iter().collect();
+ // since it failed, both should be dropped
+ assert_eq!(drop_count.load(Ordering::Relaxed), 2);
+ }
+
+ #[proptest]
+ #[cfg_attr(miri, ignore)]
+ fn undersized_proptest(#[strategy(vec_strategy(LEN))] vec: Vec<String>) {
+ let ArrayFromIter::<String, SHORT_LEN>(array) = vec.into_iter().collect();
+ prop_assert!(array.is_none());
+ }
+
+ #[proptest]
+ #[cfg_attr(miri, ignore)]
+ fn oversized_proptest(#[strategy(vec_strategy(LEN))] vec: Vec<String>) {
+ let ArrayFromIter::<String, LONG_LEN>(array) = vec.into_iter().collect();
+ prop_assert!(array.is_none());
+ }
+
+ #[proptest]
+ #[cfg_attr(miri, ignore)]
+ fn just_right_proptest(#[strategy(vec_strategy(LEN))] vec: Vec<String>) {
+ let expected: [String; LEN] = vec.clone().try_into().unwrap();
+ let ArrayFromIter(array) = vec.into_iter().collect();
+ prop_assert_eq!(array.unwrap(), expected);
+ }
+}
diff --git a/vendor/unarray/src/lib.rs b/vendor/unarray/src/lib.rs
new file mode 100644
index 000000000..a5f2b4e71
--- /dev/null
+++ b/vendor/unarray/src/lib.rs
@@ -0,0 +1,195 @@
+//! # Unarray
+//!
+//! Helper utilities for working with arrays of uninitialized memory.
+//!
+//! ## Current stable Rust
+//!
+//! Creating arrays in Rust can be somewhat painful. Currently, your best option in the general
+//! case is to allocate your elements in a `Vec`, then convert to an array:
+//! ```
+//! # use core::convert::TryInto;
+//! const LEN: usize = 1000;
+//! let mut elements = Vec::with_capacity(LEN); // heap allocation here
+//!
+//! for i in 0..LEN {
+//! elements.push(123);
+//! }
+//!
+//! let result: [i32; LEN] = elements.try_into().unwrap();
+//! ```
+//! This needlessly allocates space on the heap, which is then immediately freed. If your type
+//! implements `Copy`, and has a sensible default value, you can avoid this allocation by creating
+//! an array literal (e.g. `[0; 1000]`), then iterating over each element and setting it, but this
+//! also incurrs an unnecessary initialization cost. Why set each element to `0`, then set it
+//! again, when you could just set it once?
+//!
+//! ## `uninit_buf` and `mark_initialized`
+//!
+//! The lowest-level tools provided by this library are the pair of functions: [`uninit_buf`] and
+//! [`mark_initialized`]. These are ergonomic wrappers around the [`core::mem::MaybeUninit`] type.
+//! Roughly speaking, most uses of these functions will follow the following steps:
+//! - Stack-allocate a region of uninitialized memory with [`uninit_buf`]
+//! - Initialize each element
+//! - Unsafely declare that all elements are initialized using [`mark_initialized`]
+//!
+//! For example:
+//! ```
+//! # use unarray::*;
+//! let mut buffer = uninit_buf();
+//!
+//! for elem in &mut buffer {
+//! elem.write(123);
+//! }
+//!
+//! let result = unsafe { mark_initialized(buffer) };
+//! assert_eq!(result, [123; 1000]);
+//! ```
+//! These functions closely map onto tools provided by [`core::mem::MaybeUninit`], so should feel
+//! familiar. However, [`mark_initialized`] is an unsafe function, since it's possible to create
+//! uninitialized values that aren't wrapped in `MaybeUninit`. It's up to the programmer to make
+//! sure every element has been initialized before calling [`mark_initialized`], otherwise it's UB.
+//!
+//! For this, there are also fully safe APIs that cover some of the common patterns via an
+//! extension trait on `[T; N]`:
+//!
+//! ## `UnarrayArrayExt` extension trait
+//!
+//! ```
+//! # use unarray::*;
+//! // mapping an array via a `Result`
+//! let strings = ["123", "234"];
+//! let numbers = strings.map_result(|s| s.parse());
+//! assert_eq!(numbers, Ok([123, 234]));
+//!
+//! let bad_strings = ["123", "uh oh"];
+//! let result = bad_strings.map_result(|s| s.parse::<i32>());
+//! assert!(result.is_err()); // since one of the element fails, the whole operation fails
+//! ```
+//! There is also `map_option` for functions which return an `Option`
+//!
+//! ## Collecting iterators
+//!
+//! Iterators generally don't know their length at compile time. But it's often the case that the
+//! programmer knows the length ahead of time. In cases like this, it's common to want to collect
+//! these elements into an array, without heap allocation or initializing default elements.
+//!
+//! Arrays don't implement `FromIterator` for this very reason. So this library provides
+//! `ArrayFromIter`:
+//! ```
+//! # use unarray::*;
+//! let iter = [1, 2, 3].into_iter().map(|i| i * 2);
+//! let ArrayFromIter(array) = iter.collect(); // inferred to be `ArrayFromIter::<i32, 3>`
+//! assert_eq!(array, Some([2, 4, 6]));
+//! ```
+//! However, this can fail, since the iterator may not actually yield the right number of elements.
+//! In these cases, the inner option is `None`:
+//! ```
+//! # use unarray::*;
+//! let iter = [1, 2, 3, 4].into_iter();
+//! match iter.collect() {
+//! ArrayFromIter(Some([a, b, c])) => println!("3 elements, {a}, {b}, {c}"),
+//! ArrayFromIter(None) => println!("not 3 elements"),
+//! }
+//! ```
+//! ## `build_array-*` functions
+//!
+//! Finally, it's often the case that you want to initialize each array element based on its index.
+//! For that, [`build_array`] takes a const generic length, and a function that takes an index and
+//! returns an element, and builds the array for you:
+//! ```
+//! use unarray::*;
+//! let array: [usize; 5] = build_array(|i| i * 2);
+//! assert_eq!(array, [0, 2, 4, 6, 8]);
+//! ```
+//! There are also variants that allow fallibly constructing an array, via [`build_array_result`]
+//! or [`build_array_option`], similar to [`UnarrayArrayExt::map_result`] and [`UnarrayArrayExt::map_option`].
+
+#![cfg_attr(not(test), no_std)]
+#![deny(clippy::missing_safety_doc, missing_docs)]
+use core::mem::MaybeUninit;
+
+mod build;
+mod map;
+mod from_iter;
+#[cfg(test)]
+mod testing;
+
+pub use build::{build_array, build_array_option, build_array_result};
+pub use map::UnarrayArrayExt;
+pub use from_iter::ArrayFromIter;
+
+/// Convert a `[MaybeUninit<T>; N]` to a `[T; N]`
+///
+/// ```
+/// # use unarray::*;
+/// let mut buffer = uninit_buf::<i32, 1000>();
+///
+/// for elem in &mut buffer {
+/// elem.write(123);
+/// }
+///
+/// let result = unsafe { mark_initialized(buffer) };
+/// assert_eq!(result, [123; 1000])
+/// ```
+///
+/// This largely acts as a workaround to the fact that [`core::mem::transmute`] cannot be used with
+/// const generic arrays, as it can't prove they have the same size (even when intuitively they are
+/// the same, e.g. `[i32; N]` and `[u32; N]`).
+///
+/// This is similar to the nightly-only [`core::mem::MaybeUninit::array_assume_init`]
+///
+/// # Safety
+///
+/// Internally, this uses [`core::mem::transmute_copy`] to convert a `[MaybeUninit<T>; N]` to `[T; N]`.
+/// As such, you must make sure every element has been initialized before calling this function. If
+/// there are uninitialized elements in `src`, these will be converted to `T`s, which is UB. For
+/// example:
+/// ```no_run
+/// # use unarray::*;
+/// // ⚠️ This example produces UB ⚠️
+/// let bools = uninit_buf::<bool, 10>();
+/// let uh_oh = unsafe { mark_initialized(bools) }; // UB: creating an invalid instance
+/// if uh_oh[0] { // double UB: reading from unintiailized memory
+/// // ...
+/// }
+/// ```
+/// Even if you never use a value, it's still UB. This is especially true for types with
+/// [`core::ops::Drop`] implementations:
+/// ```no_run
+/// # use unarray::*;
+/// // ⚠️ This example produces UB ⚠️
+/// let strings = uninit_buf::<String, 10>();
+/// let uh_oh = unsafe { mark_initialized(strings) }; // UB: creating an invalid instance
+///
+/// // uh_oh is dropped here, freeing memory at random addresses
+/// ```
+pub unsafe fn mark_initialized<T, const N: usize>(src: [MaybeUninit<T>; N]) -> [T; N] {
+ core::mem::transmute_copy::<[MaybeUninit<T>; N], [T; N]>(&src)
+}
+
+/// Create an array of unintialized memory
+///
+/// This function is just a safe wrapper around `MaybeUninit::uninit().assume_init()`, which is
+/// safe when used to create a `[MaybeUninit<T>; N]`, since this type explicitly requires no
+/// initialization
+///
+/// ```
+/// # use unarray::*;
+/// let mut buffer = uninit_buf::<i32, 1000>();
+///
+/// for elem in &mut buffer {
+/// elem.write(123);
+/// }
+///
+/// let result = unsafe { mark_initialized(buffer) };
+/// assert_eq!(result, [123; 1000])
+/// ```
+///
+/// This is similar to the nightly-only [`core::mem::MaybeUninit::uninit_array`]
+pub fn uninit_buf<T, const N: usize>() -> [MaybeUninit<T>; N] {
+ // SAFETY:
+ // This is safe because we are assuming that a `[MaybeUninit<T>; N]` is initialized. However,
+ // since `MaybeUninit` doesn't require initialization, doing nothing counts as "initializing",
+ // so this is always safe
+ unsafe { MaybeUninit::uninit().assume_init() }
+}
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());
+ }
+}
diff --git a/vendor/unarray/src/testing.rs b/vendor/unarray/src/testing.rs
new file mode 100644
index 000000000..f33ada9a8
--- /dev/null
+++ b/vendor/unarray/src/testing.rs
@@ -0,0 +1,12 @@
+use core::convert::TryInto;
+
+use proptest::collection::*;
+use proptest::prelude::*;
+
+pub fn array_strategy<const N: usize>() -> impl Strategy<Value = [String; N]> {
+ vec(any::<String>(), N).prop_map(|v| v.try_into().unwrap())
+}
+
+pub fn vec_strategy(n: usize) -> impl Strategy<Value = Vec<String>> {
+ vec(any::<String>(), n)
+}