try_continue/
lib.rs

1#![warn(clippy::all, clippy::pedantic)]
2
3//! `try-continue` provides one method, [`try_continue`](`TryContinue::try_continue`),
4//! which allows you to work with iterators of type `Result<T, _>`, as if they were
5//! simply iterators of type `T`. this is is implemented for all iterators providing
6//! a `Result`. This is particularly useful if you need to map to a fallible function,
7//! and would like to continue using the iterator API to process the elements, but still
8//! know if the mapped function fails.
9//!
10//! For instance, consider a simple parser where you are provided a list of integers as
11//! strings, and you would like to count all the strings that hold even numbers. If you
12//! wanted to work with the iterator API exclusively, it may get a bit cumbersome to pass
13//! along the `Result` if an element failed to parse. Worse, doing so may preclude you
14//! from using methods such as [`Iterator::count`], as this would actually attempt to
15//! count the `Result`s, forcing you to re-implement the counting with [`Iterator::fold`].
16//! Using the [`try_continue`](`TryContinue::try_continue`) method will allow you to work
17//! with an iterator of the parsed numbers directly.
18//!
19//! ```
20//! use std::str::FromStr;
21//! use try_continue::TryContinue;
22//!
23//! fn count_even_number_strings(elements: &[&str]) -> Result<usize, <u8 as FromStr>::Err> {
24//!     elements
25//!        .iter()
26//!        .map(|&s| s.parse::<u8>())
27//!        .try_continue(|iter| iter.filter(|n| n % 2 == 0).count())
28//! }
29//!
30//! let num_evens_result = count_even_number_strings(&vec!["1", "2", "3", "24", "28"]);
31//! assert_eq!(3, num_evens_result.unwrap());
32//!
33//! let num_evens_bad_result = count_even_number_strings(&vec!["1", "2", "three", "-4", "28"]);
34//! assert!(num_evens_bad_result.is_err());
35//! ```
36
37/// Provides the [`TryContinue::try_continue`] method, which allows use of the
38/// iterator API after mapping to fallible functions.
39pub trait TryContinue<T, E>: Iterator<Item = Result<T, E>> {
40    /// Allows one to continue processing an iterator of `Result<T, _>`, as if it were
41    /// a `Result<T>`, provided that all of the elements are `Ok`. The iterator will
42    /// short-circuit if an `Err` element is encountered.
43    ///
44    /// This is particularly useful if you need to map to a fallible function, and would
45    /// like to continue using the iterator API to process the elements, but still know if
46    /// the mapped function fails.
47    ///
48    /// # Errors
49    /// The `Result` will only return an error if the given function returns one.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use try_continue::TryContinue;
55    ///
56    /// let elements = vec!["1", "2", "3", "4"];
57    /// let total = elements
58    ///     .into_iter()
59    ///     .map(str::parse::<u8>)
60    ///     .try_continue(|iter| iter.sum());
61    ///
62    /// assert_eq!(10_u8, total.unwrap());
63    /// ```
64    ///
65    /// ```
66    /// use try_continue::TryContinue;
67    ///
68    /// let elements = vec!["1", "2", "three", "4"];
69    /// let total = elements
70    ///     .into_iter()
71    ///     .map(str::parse::<u8>)
72    ///     .try_continue(|iter| iter.sum::<u8>());
73    ///
74    /// assert!(total.is_err());
75    /// ```
76    fn try_continue<F, R>(self, f: F) -> Result<R, E>
77    where
78        Self: Sized,
79        F: FnOnce(&mut TryContinueIter<Self, E>) -> R,
80    {
81        let mut iter = TryContinueIter::new(self);
82        let iteration_output = f(&mut iter);
83        iter.err.map_or(Ok(iteration_output), Err)
84    }
85}
86
87impl<T, E, I: Iterator<Item = Result<T, E>>> TryContinue<T, E> for I {}
88
89/// The iterator produced by [`TryContinue::try_continue`], which is passed to
90/// the given closure. See its docs for more information.
91pub struct TryContinueIter<I, E> {
92    iter: I,
93    err: Option<E>,
94}
95
96impl<I, E> TryContinueIter<I, E> {
97    fn new(iter: I) -> Self {
98        Self { iter, err: None }
99    }
100}
101
102impl<T, E, I: Iterator<Item = Result<T, E>>> Iterator for TryContinueIter<I, E> {
103    type Item = T;
104
105    fn next(&mut self) -> Option<Self::Item> {
106        let res = self.iter.next()?;
107        match res {
108            Ok(value) => Some(value),
109            Err(err) => {
110                self.err = Some(err);
111                None
112            }
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[derive(PartialEq, Debug)]
122    struct TestError<'a>(&'a str);
123
124    #[test]
125    fn test_can_wrap_iterator() {
126        let items = vec![1, 2, 3];
127        let res = items
128            .into_iter()
129            .map(|x| -> Result<i32, TestError> { Ok(x) })
130            .try_continue(|iter| iter.sum());
131
132        assert_eq!(6, res.unwrap());
133    }
134
135    #[test]
136    fn test_bubbles_out_error() {
137        let items = vec![1, 2, 3];
138        let res = items
139            .into_iter()
140            .map(|_| -> Result<i32, TestError> { Err(TestError("oh no this is bad")) })
141            .try_continue(|iter| iter.count());
142
143        assert_eq!(TestError("oh no this is bad"), res.unwrap_err());
144    }
145
146    #[test]
147    fn test_bubbles_out_error_from_part_way() {
148        let items = vec![1, 2, 3];
149        let res: Result<i32, TestError> = items
150            .into_iter()
151            .enumerate()
152            .map(|(i, x)| -> Result<i32, TestError> {
153                if i == 1 {
154                    Err(TestError("oh no this is bad"))
155                } else {
156                    Ok(x)
157                }
158            })
159            .try_continue(|iter| {
160                assert!(!iter.any(|n| n == 3));
161                iter.sum()
162            });
163
164        assert_eq!(TestError("oh no this is bad"), res.unwrap_err());
165    }
166
167    #[test]
168    fn test_construction_allows_type_conversions() {
169        let items = vec![1, 2, 3];
170        let res = items
171            .into_iter()
172            .map(|x| -> Result<i32, TestError> { Ok(x) })
173            // An incorrect implementation would not allow the conversion from i32 to Vec<String>
174            .try_continue(|iter| iter.map(|x| x.to_string()).collect::<Vec<String>>());
175
176        assert_eq!(
177            vec!("1".to_string(), "2".to_string(), "3".to_string()),
178            res.unwrap()
179        );
180    }
181}