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::(option) = too_many.collect(); /// assert!(option.is_none()); /// /// let too_few = vec![1, 2].into_iter(); /// let ArrayFromIter::(option) = too_few.collect(); /// assert!(option.is_none()); /// ``` pub struct ArrayFromIter(pub Option<[T; N]>); impl FromIterator for ArrayFromIter { fn from_iter>(iter: I) -> Self { let mut buffer = uninit_buf::(); 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) { let ArrayFromIter::(array) = vec.into_iter().collect(); prop_assert!(array.is_none()); } #[proptest] #[cfg_attr(miri, ignore)] fn oversized_proptest(#[strategy(vec_strategy(LEN))] vec: Vec) { let ArrayFromIter::(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) { let expected: [String; LEN] = vec.clone().try_into().unwrap(); let ArrayFromIter(array) = vec.into_iter().collect(); prop_assert_eq!(array.unwrap(), expected); } }