diff options
Diffstat (limited to 'vendor/unarray/src')
-rw-r--r-- | vendor/unarray/src/build.rs | 167 | ||||
-rw-r--r-- | vendor/unarray/src/from_iter.rs | 148 | ||||
-rw-r--r-- | vendor/unarray/src/lib.rs | 195 | ||||
-rw-r--r-- | vendor/unarray/src/map.rs | 207 | ||||
-rw-r--r-- | vendor/unarray/src/testing.rs | 12 |
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) +} |