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