From 044ddda7f94cb4cd14ecc74fb7900a16ff9990e9 Mon Sep 17 00:00:00 2001 From: Kevan Hollbach Date: Sun, 23 Jul 2023 10:52:32 -0700 Subject: [PATCH 1/3] Add map_err --- src/adaptors/map.rs | 35 +++++++++++++++++++++++++++++++++++ src/adaptors/mod.rs | 5 ++++- src/lib.rs | 21 +++++++++++++++++++++ tests/specializations.rs | 6 ++++++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index cf5e5a00d..a4166ac40 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -122,3 +122,38 @@ 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)) + } +} diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 1695bbd65..414b5f795 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}; #[allow(deprecated)] pub use self::map::MapResults; #[cfg(feature = "use_alloc")] pub use self::multi_product::*; +pub(crate) use self::map::map_err; + 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..02c199e22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,7 @@ pub mod structs { Batching, MapInto, MapOk, + MapErr, Merge, MergeBy, TakeWhileRef, @@ -928,6 +929,26 @@ 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) + } + /// “Lift” a function of the values of the current iterator so as to process /// an iterator of `Result` values instead. /// diff --git a/tests/specializations.rs b/tests/specializations.rs index 057e11c9f..d933f0d69 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -105,6 +105,12 @@ quickcheck! { } } +quickcheck! { + fn map_err(v: Vec>) -> () { + test_specializations(&v.into_iter().map_err(|u| u.checked_add(1))); + } +} + quickcheck! { fn process_results(v: Vec>) -> () { helper(v.iter().copied()); From eb738ba0ac8e6d9e5db749e51d659e68657808e4 Mon Sep 17 00:00:00 2001 From: Kevan Hollbach Date: Sun, 23 Jul 2023 10:56:09 -0700 Subject: [PATCH 2/3] Add err_into --- src/adaptors/map.rs | 35 +++++++++++++++++++++++++++++++++++ src/adaptors/mod.rs | 4 ++-- src/lib.rs | 21 +++++++++++++++++++++ tests/specializations.rs | 6 ++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index a4166ac40..446921635 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -157,3 +157,38 @@ where 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: Iterator>, + E: 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 414b5f795..d9df3ca61 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -9,13 +9,13 @@ mod map; mod multi_product; pub use self::coalesce::*; -pub use self::map::{map_into, map_ok, MapInto, MapOk, MapErr}; +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; +pub(crate) use self::map::{map_err, err_into}; use std::fmt; use std::iter::{Fuse, Peekable, FromIterator, FusedIterator}; diff --git a/src/lib.rs b/src/lib.rs index 02c199e22..5db53c78e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,7 @@ pub mod structs { MapInto, MapOk, MapErr, + ErrInto, Merge, MergeBy, TakeWhileRef, @@ -949,6 +950,26 @@ pub trait Itertools : Iterator { 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::<_, _, i64>(); + /// itertools::assert_equal(converted, vec![Ok(()), Err(5i64)]); + /// ``` + fn err_into(self) -> ErrInto + where + Self: Iterator> + Sized, + E: 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/tests/specializations.rs b/tests/specializations.rs index d933f0d69..50a68684c 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -111,6 +111,12 @@ quickcheck! { } } +quickcheck! { + fn err_into(v: Vec>) -> () { + test_specializations(&v.into_iter().err_into::<_, _, u16>()); + } +} + quickcheck! { fn process_results(v: Vec>) -> () { helper(v.iter().copied()); From d1541e2648ae3526714ca11ea5edebed232cf874 Mon Sep 17 00:00:00 2001 From: Kevan Hollbach Date: Sun, 23 Jul 2023 11:35:54 -0700 Subject: [PATCH 3/3] Improve err_into ergonomics --- src/adaptors/map.rs | 8 +++++--- src/lib.rs | 10 ++++++---- src/try_iterator.rs | 34 ++++++++++++++++++++++++++++++++++ tests/specializations.rs | 2 +- 4 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 src/try_iterator.rs diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index 446921635..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 { @@ -164,10 +166,10 @@ where pub type ErrInto = MapSpecialCase>; /// Create a new `ErrInto` iterator. -pub(crate) fn err_into(iter: I) -> ErrInto +pub(crate) fn err_into(iter: I) -> ErrInto where - I: Iterator>, - E: Into, + I: TryIterator, + ::Error: Into, { MapSpecialCase { iter, diff --git a/src/lib.rs b/src/lib.rs index 5db53c78e..db7380954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,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; } @@ -239,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; @@ -959,13 +961,13 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let iterator = vec![Ok(()), Err(5i32)].into_iter(); - /// let converted = iterator.err_into::<_, _, i64>(); + /// let converted = iterator.err_into::(); /// itertools::assert_equal(converted, vec![Ok(()), Err(5i64)]); /// ``` - fn err_into(self) -> ErrInto + fn err_into(self) -> ErrInto where - Self: Iterator> + Sized, - E: Into, + Self: traits::TryIterator + Sized, + ::Error: Into, { adaptors::err_into(self) } 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 50a68684c..7744a11b8 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -113,7 +113,7 @@ quickcheck! { quickcheck! { fn err_into(v: Vec>) -> () { - test_specializations(&v.into_iter().err_into::<_, _, u16>()); + test_specializations(&v.into_iter().err_into::()); } }