Created
January 29, 2021 02:17
-
-
Save benkay86/8960008023c62cd5cf5239c10c6fea3e to your computer and use it in GitHub Desktop.
Flatten an iterator of results with discarding errors
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! 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