stable array collectors
bendn 2025-02-07
commit 5795ca4
-rw-r--r--.gitignore1
-rw-r--r--Cargo.toml6
-rw-r--r--src/error.rs41
-rw-r--r--src/lib.rs135
-rw-r--r--src/maybe.rs25
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
+ }
+}