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}