Skip to main content

all_the_errors/
lib.rs

1#![no_std]
2//! This crate exports the [`CollectAllTheErrors`] trait and its implementation
3//! for [`Iterator<Item=Result>`] objects.
4//!
5//! When in scope, this trait adds
6//! the [`partition_results`](CollectAllTheErrors::partition_results),
7//! [`collect_results`](CollectAllTheErrors::collect_results),
8//! and
9//! [`collect_oks_or_iter_errs`](CollectAllTheErrors::collect_oks_or_iter_errs)
10//! methods to iterators over [`Result`]s.
11
12use core::iter;
13
14#[doc(hidden)]
15pub const VERSION: &str = env!("CARGO_PKG_VERSION");
16
17/// Trait for getting _all_ the errors from an iterator over results.
18///
19/// Mostly useful for its [implementation on
20/// `Iterator<Item=Result<T, E>>`](#impl-CollectAllTheErrors<T,+E>-for-Iter).
21pub trait CollectAllTheErrors<T, E> {
22    /// Partition results into success and err values.
23    ///
24    /// Unlike [`Iterator::partition`], which returns, for instance
25    /// `(Vec<Result<T, E>>, Vec<Result<T, E>>)`,
26    /// this can return `(Vec<T>, Vec<E>)`.
27    fn partition_results<Oks, Errs>(self) -> (Oks, Errs)
28    where
29        Oks: Default + Extend<T>,
30        Errs: Default + Extend<E>;
31
32    /// Returns `Ok(all_oks)` if no error occurs, otherwise `Err(all_errs)`.
33    ///
34    /// Note that the iterator used to construct the error variant
35    /// is guaranteed to be non-empty, so, e.g., if you get a
36    /// `Err(Vec<E>)`, the vec is guaranteed to have at least one element.
37    ///
38    /// Unlike [`Iterator::collect`], which has implementations that
39    /// return `Result<Vec<T>, E>` (i.e., just the first error),
40    /// implementation can return `Result<Vec<T>, Vec<E>>`
41    /// (i.e., all the errors).
42    fn collect_results<Oks, Errs>(self) -> Result<Oks, Errs>
43    where
44        Oks: Default + Extend<T>,
45        Errs: FromIterator<E>;
46
47    /// Returns `Ok(all_oks)` if no error occurs, otherwise `Err(iterator over all errors)`.
48    ///
49    /// If this returns the `Err(err_iter)` variant, the iterator guaranteed
50    /// not to be empty (i.e., calling `err_iter.next().unwrap()` once
51    /// is guaranteed not to panic).
52    fn collect_oks_or_iter_errs<Oks>(self) -> Result<Oks, impl Iterator<Item = E>>
53    where
54        Oks: Default + Extend<T>;
55}
56
57/// Primary implementation of the [`CollectAllTheErrors`] trait.
58///
59/// ## Example: collecting into `Vec`s
60///
61/// For clarity, let's assume you want to collect results into `Vec`s
62/// (see the [trait docs](CollectAllTheErrors) for the generic signature).
63///
64/// Bringing this trait into scope will give you the following
65/// extra methods on `iter: Iterator<Item=Result<T, E>>`:
66///
67/// 1. `iter.partition_results() -> (Vec<T>, Vec<E>)`
68/// 2. `iter.collect_results() -> Result<Vec<T>, Vec<E>>`
69/// 3. `iter.collect_oks_or_iter_errs() -> Result<Vec<T>, impl Iterator<Item=E>>`
70///
71/// For example:
72///
73/// ```rust
74/// use all_the_errors::CollectAllTheErrors;
75///
76/// assert_eq!(
77///     [Ok(1), Err("a"), Ok(2), Err("b")]
78///         .into_iter()
79///         .partition_results(),
80///     (vec![1, 2], vec!["a", "b"])
81/// );
82///
83/// assert_eq!(
84///     [Ok(1), Ok(2)]
85///         .into_iter()
86///         .collect_results::<Vec<_>, Vec<()>>(),
87///     Ok(vec![1, 2])
88/// );
89///
90/// assert_eq!(
91///     [Ok(1), Err("a"), Ok(2), Err("b")]
92///         .into_iter()
93///         .collect_results::<Vec<_>, Vec<_>>(),
94///     Err(vec!["a", "b"])
95/// );
96/// ```
97impl<Iter, T, E> CollectAllTheErrors<T, E> for Iter
98where
99    Iter: Iterator<Item = Result<T, E>>,
100{
101    fn partition_results<Oks, Errs>(self) -> (Oks, Errs)
102    where
103        Oks: Default + Extend<T>,
104        Errs: Default + Extend<E>,
105    {
106        let mut oks: Oks = Default::default();
107        let mut errs: Errs = Default::default();
108
109        for item in self {
110            match item {
111                Ok(ok) => oks.extend(Some(ok)),
112                Err(err) => errs.extend(Some(err)),
113            }
114        }
115
116        (oks, errs)
117    }
118
119    // Q: if we need to switch from collecting successes to failures,
120    //    could we clear() the Vec<T> then reuse it? (with unsafe, obvs)
121    // A: NO - if you used `into/from_raw_parts` to recast
122    //    the pointer, it would be UB if T and E have different alignments.
123    //    And even if you could guarantee compatible alignments,
124    //    you'd need to fix the capacity of the allocation to be an
125    //    integer multiple of E's size.
126    //    See https://doc.rust-lang.org/std/vec/struct.Vec.html#method.from_raw_parts
127    //
128    // As an alternative, we just drop the first vec before filling the second one,
129    // which *may* encourage the allocator to reuse the memory rather than
130    // making a syscall? <- complete untested conjecture
131
132    fn collect_results<Oks, Errs>(self) -> Result<Oks, Errs>
133    where
134        Oks: Default + Extend<T>,
135        Errs: FromIterator<E>,
136    {
137        self.collect_oks_or_iter_errs()
138            .map_err(|e| e.collect::<Errs>())
139    }
140
141    fn collect_oks_or_iter_errs<Oks>(mut self) -> Result<Oks, impl Iterator<Item = E>>
142    where
143        Oks: Default + Extend<T>,
144    {
145        // collect successes until iterator ends or we find an error
146        let first_err: E;
147        let mut oks: Oks = Default::default();
148        loop {
149            match self.next() {
150                None => return Ok(oks),
151                Some(Ok(ok)) => oks.extend(Some(ok)),
152                Some(Err(err)) => {
153                    first_err = err;
154                    break;
155                }
156            }
157        }
158
159        Err(iter::once(first_err).chain(self.filter_map(Result::err)))
160    }
161}