diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index cf5e5a00d..743565973 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -1,6 +1,8 @@ use std::iter::FromIterator; use std::marker::PhantomData; +use crate::traits::TryIterator; + #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct MapSpecialCase { @@ -122,3 +124,73 @@ pub fn map_into(iter: I) -> MapInto { f: MapSpecialCaseFnInto(PhantomData), } } + +/// An iterator adapter to apply a transformation within a nested `Result::Err`. +/// +/// See [`.map_err()`](crate::Itertools::map_err) for more information. +pub type MapErr = MapSpecialCase>; + +/// Create a new `MapErr` iterator. +pub(crate) fn map_err(iter: I, f: F) -> MapErr +where + I: Iterator>, + F: FnMut(E) -> E2, +{ + MapSpecialCase { + iter, + f: MapSpecialCaseFnErr(f), + } +} + +#[derive(Clone)] +pub struct MapSpecialCaseFnErr(F); + +impl std::fmt::Debug for MapSpecialCaseFnErr { + debug_fmt_fields!(MapSpecialCaseFnErr,); +} + +impl MapSpecialCaseFn> for MapSpecialCaseFnErr +where + F: FnMut(E) -> E2, +{ + type Out = Result; + + fn call(&mut self, r: Result) -> Self::Out { + r.map_err(|v| self.0(v)) + } +} + +/// An iterator adapter to convert a nested `Result::Err` using [`Into`]. +/// +/// See [`.map_err()`](crate::Itertools::map_err) for more information. +pub type ErrInto = MapSpecialCase>; + +/// Create a new `ErrInto` iterator. +pub(crate) fn err_into(iter: I) -> ErrInto +where + I: TryIterator, + ::Error: Into, +{ + MapSpecialCase { + iter, + f: MapSpecialCaseFnErrInto(PhantomData), + } +} + +#[derive(Clone)] +pub struct MapSpecialCaseFnErrInto(PhantomData); + +impl std::fmt::Debug for MapSpecialCaseFnErrInto { + debug_fmt_fields!(MapSpecialCaseFnErrInto,); +} + +impl MapSpecialCaseFn> for MapSpecialCaseFnErrInto +where + E: Into, +{ + type Out = Result; + + fn call(&mut self, r: Result) -> Self::Out { + r.map_err(Into::into) + } +} diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 1695bbd65..d9df3ca61 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -7,13 +7,16 @@ mod coalesce; mod map; mod multi_product; + pub use self::coalesce::*; -pub use self::map::{map_into, map_ok, MapInto, MapOk}; +pub use self::map::{map_into, map_ok, MapInto, MapOk, MapErr, ErrInto}; #[allow(deprecated)] pub use self::map::MapResults; #[cfg(feature = "use_alloc")] pub use self::multi_product::*; +pub(crate) use self::map::{map_err, err_into}; + use std::fmt; use std::iter::{Fuse, Peekable, FromIterator, FusedIterator}; use std::marker::PhantomData; diff --git a/src/lib.rs b/src/lib.rs index c23a65db5..db7380954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,8 @@ pub mod structs { Batching, MapInto, MapOk, + MapErr, + ErrInto, Merge, MergeBy, TakeWhileRef, @@ -162,6 +164,7 @@ pub mod structs { /// Traits helpful for using certain `Itertools` methods in generic contexts. pub mod traits { + pub use crate::try_iterator::TryIterator; pub use crate::tuple_impl::HomogeneousTuple; } @@ -237,6 +240,7 @@ mod sources; mod take_while_inclusive; #[cfg(feature = "use_alloc")] mod tee; +mod try_iterator; mod tuple_impl; #[cfg(feature = "use_std")] mod duplicates_impl; @@ -928,6 +932,46 @@ pub trait Itertools : Iterator { flatten_ok::flatten_ok(self) } + /// Return an iterator adaptor that applies the provided closure to every + /// [`Result::Err`] value. [`Result::Ok`] values are unchanged. + /// + /// # Examples + /// + /// ``` + /// use itertools::Itertools; + /// + /// let iterator = vec![Ok(41), Err(0), Ok(11)].into_iter(); + /// let mapped = iterator.map_err(|x| x + 2); + /// itertools::assert_equal(mapped, vec![Ok(41), Err(2), Ok(11)]); + /// ``` + fn map_err(self, f: F) -> MapErr + where + Self: Iterator> + Sized, + F: FnMut(E) -> E2, + { + adaptors::map_err(self, f) + } + + /// Return an iterator adaptor that converts every [`Result::Err`] value + /// using the [`Into`] trait. + /// + /// # Examples + /// + /// ``` + /// use itertools::Itertools; + /// + /// let iterator = vec![Ok(()), Err(5i32)].into_iter(); + /// let converted = iterator.err_into::(); + /// itertools::assert_equal(converted, vec![Ok(()), Err(5i64)]); + /// ``` + fn err_into(self) -> ErrInto + where + Self: traits::TryIterator + Sized, + ::Error: Into, + { + adaptors::err_into(self) + } + /// “Lift” a function of the values of the current iterator so as to process /// an iterator of `Result` values instead. /// diff --git a/src/try_iterator.rs b/src/try_iterator.rs new file mode 100644 index 000000000..147c70cee --- /dev/null +++ b/src/try_iterator.rs @@ -0,0 +1,34 @@ +mod private { + pub trait Sealed {} + impl Sealed for I where I: Iterator> {} +} + +/// Helper trait automatically implemented for [`Iterator`]s of [`Result`]s. +/// +/// Can be useful for specifying certain trait bounds more concisely. Take +/// [`.err_into()`][err_into] for example: +/// +/// Without [`TryIterator`], [`err_into`][err_into] would have to be generic +/// over 3 type parameters: the type of [`Result::Ok`] values, the type of +/// [`Result::Err`] values, and the type to convert errors into. Usage would +/// look like this: `my_iterator.err_into<_, _, E>()`. +/// +/// Using [`TryIterator`], [`err_into`][err_into] can be generic over a single +/// type parameter, and called like this: `my_iterator.err_into()`. +/// +/// [err_into]: crate::Itertools::err_into +pub trait TryIterator: Iterator + private::Sealed { + /// The type of [`Result::Ok`] values yielded by this [`Iterator`]. + type Ok; + + /// The type of [`Result::Err`] values yielded by this [`Iterator`]. + type Error; +} + +impl TryIterator for I +where + I: Iterator>, +{ + type Ok = T; + type Error = E; +} diff --git a/tests/specializations.rs b/tests/specializations.rs index 057e11c9f..7744a11b8 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -105,6 +105,18 @@ quickcheck! { } } +quickcheck! { + fn map_err(v: Vec>) -> () { + test_specializations(&v.into_iter().map_err(|u| u.checked_add(1))); + } +} + +quickcheck! { + fn err_into(v: Vec>) -> () { + test_specializations(&v.into_iter().err_into::()); + } +} + quickcheck! { fn process_results(v: Vec>) -> () { helper(v.iter().copied());