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}