stable array collectors
init
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | src/error.rs | 41 | ||||
| -rw-r--r-- | src/lib.rs | 135 | ||||
| -rw-r--r-- | src/maybe.rs | 25 |
5 files changed, 208 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffa3bbd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Cargo.lock
\ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..787bcc4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "collar" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..2f4e3c4 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,41 @@ +/// This error is returned by [`try_collect_array`](super::CollectArray::try_collect_array) +#[derive(Clone, Copy, Hash)] +pub struct Error<const N: usize, E> { + /// Error returned by <code>[next](Iterator::next)()?.error</code> (`()` if [`None`]). + pub error: Option<E>, + /// Point of error. + pub at: usize, +} + +impl<const N: usize, const O: usize, E: PartialEq> PartialEq<Error<O, E>> for Error<N, E> { + fn eq(&self, other: &Error<O, E>) -> bool { + (self.error == other.error) & (self.at == other.at) + } +} + +impl<const N: usize, E: std::fmt::Display> std::fmt::Display for Error<N, E> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match (&self.error, &self.at) { + (Some(x), at) => write!(f, "{x} @ {at} of {N}"), + (None, at) => write!( + f, + "couldnt fill array of length {N}, only had {at} elements.", + ), + } + } +} + +impl<const N: usize, E: std::fmt::Debug> std::fmt::Debug for Error<N, E> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match (&self.error, &self.at) { + (Some(x), at) => write!(f, "{x:?} @ {at} of {N}"), + (None, at) => write!(f, "Size(wanted {N}, had {at})"), + } + } +} + +impl<const N: usize, E: std::error::Error + 'static> std::error::Error for Error<N, E> { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(self.error.as_ref()?) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..74820f4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,135 @@ +pub use error::Error; +use std::mem::{ManuallyDrop as MD, MaybeUninit as MU}; +const unsafe fn transmute_unchecked<T, U>(value: T) -> U { + const { assert!(size_of::<T>() == size_of::<U>()) } + #[repr(C)] + union Transmute<T, U> { + t: MD<T>, + u: MD<U>, + } + unsafe { MD::into_inner(Transmute { t: MD::new(value) }.u) } +} +mod error; +mod maybe; +use maybe::Maybe; +/// Collect to an array. +pub trait CollectArray: Iterator + Sized { + /// Lets you collect an iterator into a fixed length array with no vec allocation. + /// Handle remainder as you wish. Does not consume the iterator. + /// + /// # Panics + /// + /// when <code>[next](Iterator::next)() is [None]</code> before the array is filled. + /// for a non panicking alternative, see [`collect_array_checked`](CollectArray::collect_array_checked). + /// + /// ``` + /// use collar::*; + /// let array = (0usize..).map(|x| x * 2).collect_array(); + /// // indexes are: 0 1 2 3 4 5 6 7 + /// assert_eq!(array, [0, 2, 4, 6, 8, 10, 12, 14]); + /// ``` + fn collect_array<const N: usize>(&mut self) -> [Self::Item; N] { + self.collect_array_checked() + .unwrap_or_else(|x| panic!("couldnt fill buffer of length {N} only had {x} elements")) + } + /// Non panicking version of [`collect_array`](CollectArray::collect_array). + /// + /// Lets you collect an iterator into a fixed length array with no vec allocation, with no panics. + /// If the iterator returns [`None`] at any point, returns <code>[Err]\(elements filed\)</code>. + /// + /// If you wish to simply populate the array with [`None`] if the iterator returns [`None`], use [`items`](CollectArray::items). + /// + /// ``` + /// use collar::*; + /// let array: Result<[u8; 10], usize> = std::iter::repeat(5).take(3).collect_array_checked(); + /// // does not fill array -> produces `Err`, with number of elements filled. + /// assert_eq!(array, Err(3)); + /// ``` + fn collect_array_checked<const N: usize>(&mut self) -> Result<[Self::Item; N], usize> { + let mut out = [const { MU::uninit() }; N]; + // initialize each element + for elem in 0..N { + out[elem] = MU::new(match self.next() { + Some(x) => x, + None => { + for item in &mut out[..elem] { + // drop initialized elements + unsafe { item.assume_init_drop() }; + } + return Err(elem); + } + }); + } + // SAFETY: all initialized + Ok(unsafe { transmute_unchecked(out) }) + } + + /// Creates an array [T; N] where each fallible (i.e [`Option`] or [`Result`]) element is begotten from [`next`](Iterator::next). + /// Unlike [`collect_array`](CollectArray::collect_array), where the element creation can't fail, this version will return an error if any element creation was unsuccessful (returned [`Err`] or [`None`]). + /// In the case where the iterator ran out of elements, this returns a [`CollectorError::Amount`] + /// + /// The return type of this function depends on the [`Item`](Iterator::Item) of this [`Iterator`]. + /// If you return `Result<T, E>` from the closure, you'll get a `Result<[T; N], CollectorError<E>>`. + /// If you return `Option<T>` from the closure, you'll get an `Result<[T; N], CollectorError<()>>`. + /// ``` + /// use collar::CollectArray; + /// let array: Result<[i8; 200], _> = (0..).map(|x| x.try_into()).try_collect_array(); + /// assert_eq!(array.unwrap_err().at, 128); + /// + /// // note the ok(); the try trait is still unstable. (so this is a Result<_, ()>::ok) + /// let array: Option<[_; 4]> = (0usize..).map(|i| i.checked_add(100)).try_collect_array().ok(); + /// assert_eq!(array, Some([100, 101, 102, 103])); + /// + /// let array: Option<[_; 4]> = (0usize..).map(|i| i.checked_sub(100)).try_collect_array().ok(); + /// assert_eq!(array, None); + /// ``` + fn try_collect_array<const N: usize>( + &mut self, + ) -> Result<[<Self::Item as Maybe>::Unwrap; N], Error<N, <Self::Item as Maybe>::Or>> + where + <Self as Iterator>::Item: Maybe, + { + let mut out = [const { MU::uninit() }; N]; + // initialize each element of `out` + for elem in 0..N { + let e = match self + .next() + .ok_or(Error { + at: elem, + error: None, + }) + .and_then(|x| { + x.asr().map_err(|x| Error { + at: elem, + error: Some(x), + }) + }) { + Ok(x) => x, + Err(x) => { + for item in &mut out[..elem] { + // drop each previously initialized item + unsafe { item.assume_init_drop() }; + } + return Err(x); + } + }; + out[elem] = MU::new(e); + } + // SAFETY: each element has been initialized + Ok(unsafe { transmute_unchecked(out) }) + } + + /// This function fills an array with this iterators elements. + /// It will always return (unless the iterator panics). + /// ``` + /// use collar::*; + /// assert_eq!( + /// (0..).items::<5>(), + /// (0..).map(Some).collect_array::<5>(), + /// ) + /// ``` + fn items<const N: usize>(&mut self) -> [Option<Self::Item>; N] { + std::array::from_fn(|_| self.next()) + } +} +impl<I: Iterator> CollectArray for I {} diff --git a/src/maybe.rs b/src/maybe.rs new file mode 100644 index 0000000..8e2bb23 --- /dev/null +++ b/src/maybe.rs @@ -0,0 +1,25 @@ +#[diagnostic::on_unimplemented( + message = "this is a helper for [Option, Result].", + label = "consider using collect_array_checked", + note = "you probably want to get a `None` if the iterator isnt big enough" +)] +#[doc(hidden)] +pub trait Maybe { + type Unwrap; + type Or; + fn asr(self) -> Result<Self::Unwrap, Self::Or>; +} +impl<T> Maybe for Option<T> { + type Unwrap = T; + type Or = (); + fn asr(self) -> Result<Self::Unwrap, Self::Or> { + self.ok_or(()) + } +} +impl<T, E> Maybe for Result<T, E> { + type Unwrap = T; + type Or = E; + fn asr(self) -> Result<Self::Unwrap, Self::Or> { + self + } +} |