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}