Skip to main content

alux_sdk/
collection.rs

1use alux_ext::ext;
2use std::array::TryFromSliceError;
3
4/// Extends iterators with exact and fallible collection helpers.
5#[ext(name = IntoIteratorExt)]
6pub impl<This> This {
7    /// A variant of the `try_collect` and `collect_vec` combined function from the `Itertools`
8    /// library. The materialized value is a [`Vec`] inside a [`Result`] object.
9    ///
10    /// Returns the first error without collecting later values.
11    ///
12    /// ```
13    /// use alux_sdk::*;
14    ///
15    /// let values = [Ok::<_, &str>(1), Ok(2), Ok(3)]
16    ///     .into_iter()
17    ///     .try_collect_vec();
18    ///
19    /// assert_eq!(values, Ok(vec![1, 2, 3]));
20    /// ```
21    fn try_collect_vec<T, E>(self) -> Result<Vec<T>, E>
22    where
23        This: Iterator<Item = Result<T, E>>,
24    {
25        self.collect()
26    }
27
28    /// Takes exactly `n` elements from the iterator.
29    ///
30    /// Returns `Some(Vec<T>)` if the iterator yields exactly `n` elements,
31    /// otherwise returns `None`.
32    ///
33    /// ```
34    /// use alux_sdk::*;
35    ///
36    /// let v = [1, 2, 3];
37    ///
38    /// assert_eq!(v.iter().copied().collect_exact(3), Some(vec![1, 2, 3]));
39    /// assert_eq!(v.iter().copied().collect_exact(4), None);
40    /// assert_eq!(v.iter().copied().collect_exact(2), None);
41    /// ```
42    fn collect_exact<T>(self, n: usize) -> Option<Vec<T>>
43    where
44        This: Iterator<Item = T>,
45    {
46        let mut iterator = self;
47        let result = iterator.by_ref().take(n).collect::<Vec<_>>();
48
49        (result.len() == n && iterator.next().is_none()).then_some(result)
50    }
51
52    /// Efficiently splits an iterator of `Result<(A, B), E>` into two `Vec`s, short-circuiting on error.
53    ///
54    /// Consumes the iterator, collecting all `A` elements into one vector and all `B` elements into another,
55    /// without first allocating an intermediate vector of tuples. Returns the tuple `(Vec<A>, Vec<B>)` on success,
56    /// or the first error encountered.
57    ///
58    /// ```
59    /// use alux_sdk::*;
60    ///
61    /// let input = vec![Ok((1, "x")), Ok((2, "y"))];
62    /// let (xs, ys) = input.try_unzip::<_, _, ()>().unwrap();
63    ///
64    /// assert_eq!(xs, vec![1, 2]);
65    /// assert_eq!(ys, vec!["x", "y"]);
66    /// ```
67    fn try_unzip<A, B, E>(self) -> Result<(Vec<A>, Vec<B>), E>
68    where
69        This: IntoIterator<Item = Result<(A, B), E>>,
70    {
71        // Allocate two output vectors; their size will grow as we process items.
72        let mut va = vec![];
73        let mut vb = vec![];
74
75        // Iterate over each item in the input iterator.
76        for res in self {
77            // Propagate the first error immediately, if encountered.
78            let (a, b) = res?;
79            // Push the first and second element to their respective vectors.
80            va.push(a);
81            vb.push(b);
82        }
83
84        // Return both vectors if all results were successful.
85        Ok((va, vb))
86    }
87}
88
89/// Extends immutable slices with prefix conversion to arrays.
90#[ext(name = SliceExt)]
91pub impl<'a, Item> &'a [Item] {
92    /// Helper method to get a constant-length slice from a collection of equal or greater size.
93    ///
94    /// For the mutable variant, see [`SliceMutExt::try_to_const_mut`].
95    ///
96    /// ```
97    /// use alux_sdk::*;
98    ///
99    /// let inp = &[1, 2, 3][..];
100    ///
101    /// let res = inp.try_to_const();
102    /// assert_eq!(&[1, 2], res.unwrap());
103    ///
104    /// let res = inp.try_to_const::<3>();
105    /// assert_eq!(&[1, 2, 3], res.unwrap());
106    ///
107    /// let res = inp.try_to_const::<4>();
108    /// assert!(res.is_err());
109    /// ```
110    #[allow(clippy::inline_always)]
111    #[inline(always)]
112    fn try_to_const<const N: usize>(self) -> Result<&'a [Item; N], TryFromSliceError> {
113        if self.len() > N { self[..N].try_into() } else { self.try_into() }
114    }
115}
116
117/// Extends mutable slices with prefix conversion to arrays.
118#[ext(name = SliceMutExt)]
119pub impl<'a, Item> &'a mut [Item] {
120    /// Helper method to get a constant-length slice from a collection of equal or greater size.
121    ///
122    /// It's the mutable variant of [`SliceExt::try_to_const`] method.
123    ///
124    /// ```
125    /// use alux_sdk::*;
126    ///
127    /// let inp = &mut [1, 2, 3][..];
128    ///
129    /// let res = inp.try_to_const_mut();
130    /// assert_eq!(&mut [1, 2], res.unwrap());
131    ///
132    /// let res = inp.try_to_const_mut();
133    /// assert_eq!(&mut [1, 2, 3], res.unwrap());
134    ///
135    /// let res = inp.try_to_const_mut::<4>();
136    /// assert!(res.is_err());
137    /// ```
138    #[allow(clippy::inline_always)]
139    #[inline(always)]
140    fn try_to_const_mut<const N: usize>(self) -> Result<&'a mut [Item; N], TryFromSliceError> {
141        if self.len() > N { (&mut self[..N]).try_into() } else { self.try_into() }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::{IntoIteratorExt, SliceExt, SliceMutExt};
148
149    #[test]
150    fn try_to_const() {
151        let inp = &[1, 2, 3][..];
152
153        let res = inp.try_to_const();
154        assert_eq!(&[1, 2], res.unwrap());
155
156        let res = inp.try_to_const();
157        assert_eq!(&[1, 2, 3], res.unwrap());
158
159        let res = inp.try_to_const::<4>();
160        assert!(res.is_err());
161    }
162
163    #[test]
164    fn try_to_const_mut() {
165        let inp = &mut [1, 2, 3][..];
166
167        let res = inp.try_to_const_mut();
168        assert_eq!(&mut [1, 2], res.unwrap());
169
170        let res = inp.try_to_const_mut();
171        assert_eq!(&mut [1, 2, 3], res.unwrap());
172
173        let res = inp.try_to_const_mut::<4>();
174        assert!(res.is_err());
175    }
176
177    #[test]
178    fn collect_exact_accepts_only_the_requested_shape() {
179        assert_eq!((0..3).collect_exact(3), Some(vec![0, 1, 2]));
180        assert_eq!((0..3).collect_exact(2), None);
181        assert_eq!((0..3).collect_exact(4), None);
182    }
183
184    #[test]
185    fn try_collect_vec_stops_at_the_first_error() {
186        let result = [Ok(1), Ok(2), Err("stop"), Ok(3)].into_iter().try_collect_vec();
187        assert_eq!(result, Err("stop"));
188    }
189
190    #[test]
191    fn try_unzip_preserves_pair_order() {
192        let result = [Ok::<_, ()>((1, "a")), Ok((2, "b"))].into_iter().try_unzip();
193        assert_eq!(result, Ok((vec![1, 2], vec!["a", "b"])));
194    }
195
196    #[test]
197    fn try_unzip_stops_at_the_first_error() {
198        let result = [Ok((1, "a")), Err("stop"), Ok((3, "c"))].into_iter().try_unzip();
199        assert_eq!(result, Err("stop"));
200    }
201
202    #[test]
203    fn try_to_const_borrows_a_prefix() {
204        assert_eq!([1, 2, 3].as_slice().try_to_const::<2>().expect("two-item prefix"), &[1, 2]);
205        assert!([1, 2, 3].as_slice().try_to_const::<4>().is_err());
206    }
207
208    #[test]
209    fn try_to_const_mut_borrows_a_mutable_prefix() {
210        let mut values = [1, 2, 3];
211        let prefix = values.as_mut_slice().try_to_const_mut::<2>().expect("two-item mutable prefix");
212        prefix[0] = 7;
213        assert_eq!(values, [7, 2, 3]);
214    }
215}