Skip to content

Instantly share code, notes, and snippets.

@benkay86
Created January 29, 2021 02:17
Show Gist options
  • Save benkay86/8960008023c62cd5cf5239c10c6fea3e to your computer and use it in GitHub Desktop.
Save benkay86/8960008023c62cd5cf5239c10c6fea3e to your computer and use it in GitHub Desktop.
Flatten an iterator of results with discarding errors
//! The default [`Result::into_iter()`] produces an iterator which yields `None`
//! on `Err`, effectively ignoring/discarding errors. For example:
//!
//! ```
//! let v: Vec<Result<Vec<Result<i32,i32>>,i32>> = vec![
//! Ok(vec![Ok(1), Ok(2), Err(3)]),
//! Err(4),
//! Ok(vec![Ok(5), Ok(6)]),
//! ];
//! let v: Vec<Result<i32, i32>> = v.into_iter().flatten().flatten().collect();
//! assert!(v == vec![Ok(1), Ok(2), Err(3), Ok(5), Ok(6)] );
//! // The `Err(4)` was ignored.
//! ```
//!
//! This module provides an alternative iterator and combinator method for
//! flattening an iterator of results without discarding errors.
//! See [`FlattenResultIterExt::flat_iter()`] for examples.
/// Alternative iterator over [`std::result::Result`] for flattening an iterator
/// of results without discarding errors. The default
/// [`Result::into_iter()`] produces an iterator which yields
/// `None` for an `Err`, effectively ignoring/discarding errors. This iterator
/// yields `Err` exactly once, otherwise it yields an iterator over the `Ok`.
/// See [`FlattenResultIterExt::flat_iter()`] for examples.
pub struct FlattenResultIter<I,E> {
ok_iter: Option<I>,
err: Option<E>,
}
impl<I,II,E> From<Result<II,E>> for FlattenResultIter<I,E>
where
II: IntoIterator<IntoIter = I>
{
fn from(result: Result<II,E>) -> Self {
match result {
Ok(ok) => Self{ok_iter: Some(ok.into_iter()), err: None},
Err(err) => Self{ok_iter: None, err: Some(err)}
}
}
}
impl<I,E,T> Iterator for FlattenResultIter<I,E>
where
I: Iterator<Item = Result<T,E>>
{
type Item = Result<T,E>;
fn next(&mut self) -> Option<Self::Item> {
let err = std::mem::take(&mut self.err);
match err {
Some(err) => {
Some(Err(err))
},
None => match &mut self.ok_iter {
Some(ok_iter) => ok_iter.next(),
None => None
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match &self.ok_iter {
Some(ok_iter) => ok_iter.size_hint(),
None => match self.err.is_some() {
true => (1, Some(1)),
false => (0, Some(0))
}
}
}
}
/// Extension trait to use [`FlattenResultIter`] as an iterator combinator.
/// See [`FlattenResultIterExt::flat_iter()`] for examples.
pub trait FlattenResultIterExt {
/// Type of inner iterator.
type Iterator;
/// Type of inner error.
type Error;
/// Flatten an iterator of results without discarding the errors. for
/// example:
///
/// ```
/// use flatten_result::FlattenResultIterExt;
/// let v: Vec<Result<Vec<Result<i32,i32>>,i32>> = vec![
/// Ok(vec![Ok(1), Ok(2), Err(3)]),
/// Ok(vec![Ok(4), Ok(5)]),
/// Err(6)
/// ];
/// let v: Vec<Result<i32,i32>> = v
/// .into_iter()
/// .flat_map(|r| r.flat_iter())
/// .collect();
/// assert!(v == vec![Ok(1), Ok(2), Err(3), Ok(4), Ok(5), Err(6)]);
/// ```
///
/// The `Err` type for the outer and inner `Result`s must be the same. If
/// they are not the same type you can map the inner type to the outer like
/// this:
///
/// ```
/// # use flatten_result::FlattenResultIterExt;
/// let v: Vec<Result<Vec<Result<i32,u32>>,String>> = vec![
/// Ok(vec![Ok(1), Ok(2), Err(3)]),
/// Ok(vec![Ok(4), Ok(5)]),
/// Err("6".to_string())
/// ];
/// let v: Vec<Result<i32,String>> = v
/// .into_iter()
/// .flat_map(|r|
/// // Map inner `Result<_,u32>` to outer `Result<_,String>`.
/// r.and_then(|inner_vec| Ok(
/// inner_vec.into_iter().map(|inner_res|
/// inner_res.or_else(|inner_u32|
/// Err(inner_u32.to_string())
/// )
/// )
/// ))
/// // Now that types are uniform can flatten iterator of results.
/// .flat_iter()
/// )
/// .collect();
/// assert!(v == vec![Ok(1), Ok(2), Err("3".into()), Ok(4), Ok(5), Err("6".into())]);
/// ```
fn flat_iter(self) -> FlattenResultIter<Self::Iterator,Self::Error>;
}
impl<I,II,E> FlattenResultIterExt for Result<II,E>
where
II: IntoIterator<IntoIter = I>
{
type Iterator = I;
type Error = E;
fn flat_iter(self) -> FlattenResultIter<Self::Iterator,Self::Error> {
FlattenResultIter::from(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flat_iter_uniform_type() {
let v: Vec<Result<Vec<Result<i32,i32>>,i32>> = vec![
Ok(vec![Ok(1), Ok(2), Err(3)]),
Ok(vec![Ok(4), Ok(5)]),
Err(6)
];
let v: Vec<Result<i32,i32>> = v
.into_iter()
.flat_map(|r| r.flat_iter())
.collect();
assert!(v == vec![Ok(1), Ok(2), Err(3), Ok(4), Ok(5), Err(6)]);
}
#[test]
fn test_flat_iter_thread() {
let v: Vec<Result<Vec<Result<i32,i32>>,i32>> = vec![
Ok(vec![Ok(1), Ok(2), Err(3)]),
Ok(vec![Ok(4), Ok(5)]),
Err(6)
];
let i = v.into_iter().flat_map(|r| r.flat_iter());
let v: Vec<Result<i32,i32>> = std::thread::spawn(move || {
i.collect()
}).join().expect("The thread panicked.");
assert!(v == vec![Ok(1), Ok(2), Err(3), Ok(4), Ok(5), Err(6)]);
}
#[test]
fn test_flat_iter_diff_types() {
let v: Vec<Result<Vec<Result<i32,u32>>,String>> = vec![
Ok(vec![Ok(1), Ok(2), Err(3)]),
Ok(vec![Ok(4), Ok(5)]),
Err("6".into())
];
let v: Vec<Result<i32,String>> = v
.into_iter()
.flat_map(|r|
r.and_then(|inner_vec| Ok(
inner_vec.into_iter().map(|inner_res|
inner_res.or_else(|inner_u32|
Err(inner_u32.to_string())
)
)
))
.flat_iter()
)
.collect();
assert!(v == vec![Ok(1), Ok(2), Err("3".into()), Ok(4), Ok(5), Err("6".into())]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment