Skip to main content

commonware_utils/
iter.rs

1//! Iterator types with additional invariants.
2
3use core::iter::{Chain, Once, once};
4
5/// An iterator source guaranteed to yield at least one item.
6///
7/// # Examples
8///
9/// ```
10/// use commonware_utils::iter::NonEmpty;
11///
12/// let items = NonEmpty::try_new([1, 2, 3].into_iter()).expect("items are non-empty");
13/// assert_eq!(items.into_iter().collect::<Vec<_>>(), vec![1, 2, 3]);
14/// ```
15#[derive(Clone, Debug)]
16pub struct NonEmpty<I: Iterator> {
17    first: I::Item,
18    rest: I,
19}
20
21impl<I: Iterator> NonEmpty<I> {
22    /// Creates a non-empty iterator from a first item and the remaining items.
23    pub const fn new(first: I::Item, rest: I) -> Self {
24        Self { first, rest }
25    }
26
27    /// Creates a non-empty iterator from `items`, or returns `None` when it is empty.
28    pub fn try_new(mut items: I) -> Option<Self> {
29        let first = items.next()?;
30        Some(Self::new(first, items))
31    }
32
33    /// Consumes this source, returning its first item and remaining iterator.
34    pub fn into_parts(self) -> (I::Item, I) {
35        (self.first, self.rest)
36    }
37}
38
39impl<I: Iterator> IntoIterator for NonEmpty<I> {
40    type Item = I::Item;
41    type IntoIter = Chain<Once<I::Item>, I>;
42
43    fn into_iter(self) -> Self::IntoIter {
44        once(self.first).chain(self.rest)
45    }
46}
47
48/// Creates a [`NonEmpty`] iterator from one or more items.
49///
50/// # Examples
51///
52/// ```
53/// use commonware_utils::non_empty;
54///
55/// let items = non_empty![1, 2, 3];
56/// assert_eq!(items.into_iter().collect::<Vec<_>>(), vec![1, 2, 3]);
57///
58/// let items = non_empty![@1..4];
59/// assert_eq!(items.into_iter().collect::<Vec<_>>(), vec![1, 2, 3]);
60/// ```
61///
62/// ```compile_fail
63/// use commonware_utils::non_empty;
64///
65/// let empty = non_empty![];
66/// ```
67///
68/// # Panics
69///
70/// The `@` form panics if the provided iterator is empty.
71#[cfg(not(any(
72    commonware_stability_GAMMA,
73    commonware_stability_DELTA,
74    commonware_stability_EPSILON,
75    commonware_stability_RESERVED
76)))] // BETA
77#[macro_export]
78macro_rules! non_empty {
79    (@$items:expr) => {{
80        $crate::iter::NonEmpty::try_new(::core::iter::IntoIterator::into_iter($items))
81            .expect("iterator must be non-empty")
82    }};
83    ($first:expr $(, $rest:expr)* $(,)?) => {
84        $crate::iter::NonEmpty::new(
85            $first,
86            ::core::iter::IntoIterator::into_iter([$($rest),*]),
87        )
88    };
89}
90
91/// Zips two iterators, panicking if one is exhausted before the other.
92///
93/// Use this over [`Iterator::zip`] when equal lengths are an invariant: `zip` silently truncates
94/// to the shorter side, hiding the mismatch.
95///
96/// # Examples
97///
98/// ```
99/// use commonware_utils::iter::zip_eq;
100///
101/// let pairs: Vec<_> = zip_eq([1, 2], ["a", "b"]).collect();
102/// assert_eq!(pairs, vec![(1, "a"), (2, "b")]);
103/// ```
104pub fn zip_eq<A: IntoIterator, B: IntoIterator>(a: A, b: B) -> ZipEq<A::IntoIter, B::IntoIter> {
105    ZipEq {
106        a: a.into_iter(),
107        b: b.into_iter(),
108    }
109}
110
111/// See [`zip_eq`].
112#[derive(Clone, Debug)]
113pub struct ZipEq<A, B> {
114    a: A,
115    b: B,
116}
117
118impl<A: Iterator, B: Iterator> Iterator for ZipEq<A, B> {
119    type Item = (A::Item, B::Item);
120
121    fn next(&mut self) -> Option<Self::Item> {
122        match (self.a.next(), self.b.next()) {
123            (Some(a), Some(b)) => Some((a, b)),
124            (None, None) => None,
125            (Some(_), None) => panic!("zip_eq: right iterator exhausted first"),
126            (None, Some(_)) => panic!("zip_eq: left iterator exhausted first"),
127        }
128    }
129
130    fn size_hint(&self) -> (usize, Option<usize>) {
131        let (a_low, a_high) = self.a.size_hint();
132        let (b_low, b_high) = self.b.size_hint();
133        let high = match (a_high, b_high) {
134            (Some(a), Some(b)) => Some(a.min(b)),
135            (Some(a), None) => Some(a),
136            (None, Some(b)) => Some(b),
137            (None, None) => None,
138        };
139        (a_low.min(b_low), high)
140    }
141}
142
143impl<A: ExactSizeIterator, B: ExactSizeIterator> ExactSizeIterator for ZipEq<A, B> {}
144
145#[cfg(test)]
146mod tests {
147    use super::{NonEmpty, zip_eq};
148
149    #[test]
150    fn try_new_rejects_empty() {
151        assert!(NonEmpty::try_new(core::iter::empty::<u8>()).is_none());
152    }
153
154    #[test]
155    fn iteration_preserves_every_item() {
156        let items = NonEmpty::try_new([1, 2, 3].into_iter()).expect("items are non-empty");
157        assert_eq!(items.into_iter().collect::<Vec<_>>(), vec![1, 2, 3]);
158    }
159
160    #[test]
161    fn into_parts_separates_first_from_rest() {
162        let items = NonEmpty::new(1, [2, 3].into_iter());
163        let (first, rest) = items.into_parts();
164
165        assert_eq!(first, 1);
166        assert_eq!(rest.collect::<Vec<_>>(), vec![2, 3]);
167    }
168
169    #[test]
170    fn macro_constructs_non_empty_iterators() {
171        assert_eq!(non_empty![1].into_iter().collect::<Vec<_>>(), vec![1]);
172        assert_eq!(
173            non_empty![1, 2, 3].into_iter().collect::<Vec<_>>(),
174            vec![1, 2, 3]
175        );
176        assert_eq!(
177            non_empty![@1..4].into_iter().collect::<Vec<_>>(),
178            vec![1, 2, 3]
179        );
180    }
181
182    #[test]
183    fn zip_eq_pairs_equal_lengths() {
184        let pairs: Vec<_> = zip_eq([1, 2, 3], ["a", "b", "c"]).collect();
185
186        assert_eq!(pairs, vec![(1, "a"), (2, "b"), (3, "c")]);
187    }
188
189    #[test]
190    #[should_panic(expected = "right iterator exhausted first")]
191    fn zip_eq_panics_when_right_is_shorter() {
192        zip_eq([1, 2], [1]).count();
193    }
194
195    #[test]
196    #[should_panic(expected = "left iterator exhausted first")]
197    fn zip_eq_panics_when_left_is_shorter() {
198        zip_eq([1], [1, 2]).count();
199    }
200
201    #[test]
202    #[should_panic(expected = "iterator must be non-empty")]
203    fn macro_rejects_empty_iterators() {
204        let _ = non_empty![@core::iter::empty::<u8>()];
205    }
206
207    mod colliding_method {
208        trait CollidingIntoIterator {
209            fn into_iter(self);
210        }
211
212        impl<T, const N: usize> CollidingIntoIterator for [T; N] {
213            fn into_iter(self) {}
214        }
215
216        #[test]
217        fn macro_ignores_colliding_into_iterator_methods() {
218            CollidingIntoIterator::into_iter([0]);
219
220            assert_eq!(
221                non_empty![1, 2, 3].into_iter().collect::<Vec<_>>(),
222                vec![1, 2, 3]
223            );
224            assert_eq!(
225                non_empty![@[1, 2, 3]].into_iter().collect::<Vec<_>>(),
226                vec![1, 2, 3]
227            );
228        }
229    }
230}