From aef59e4fb87fe8c79d3c5d6e7164c02c84fe2b8b Mon Sep 17 00:00:00 2001 From: kit Date: Sun, 11 Jul 2021 15:43:30 +1000 Subject: [PATCH] Add a `try_reduce` method to the Iterator trait --- library/core/src/iter/traits/iterator.rs | 80 ++++++++++++++++++++++ library/core/tests/iter/traits/iterator.rs | 28 ++++++++ library/core/tests/lib.rs | 1 + 3 files changed, 109 insertions(+) diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index 88e7623eba1ce..267fa40679836 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -2216,6 +2216,86 @@ pub trait Iterator { Some(self.fold(first, f)) } + /// Reduces the elements to a single one by repeatedly applying a reducing operation. If the + /// closure returns a failure, the failure is propagated back to the caller immediately. + /// + /// The return type of this method depends on the return type of the closure. If the closure + /// returns `Result`, then this function will return `Result, + /// E>`. If the closure returns `Option`, then this function will return + /// `Option>`. + /// + /// When called on an empty iterator, this function will return either `Some(None)` or + /// `Ok(None)` depending on the type of the provided closure. + /// + /// For iterators with at least one element, this is essentially the same as calling + /// [`try_fold()`] with the first element of the iterator as the initial accumulator value. + /// + /// [`try_fold()`]: Iterator::try_fold + /// + /// # Examples + /// + /// Safely calculate the sum of a series of numbers: + /// + /// ``` + /// #![feature(iterator_try_reduce)] + /// + /// let numbers: Vec = vec![10, 20, 5, 23, 0]; + /// let sum = numbers.into_iter().try_reduce(|x, y| x.checked_add(y)); + /// assert_eq!(sum, Some(Some(58))); + /// ``` + /// + /// Determine when a reduction short circuited: + /// + /// ``` + /// #![feature(iterator_try_reduce)] + /// + /// let numbers = vec![1, 2, 3, usize::MAX, 4, 5]; + /// let sum = numbers.into_iter().try_reduce(|x, y| x.checked_add(y)); + /// assert_eq!(sum, None); + /// ``` + /// + /// Determine when a reduction was not performed because there are no elements: + /// + /// ``` + /// #![feature(iterator_try_reduce)] + /// + /// let numbers: Vec = Vec::new(); + /// let sum = numbers.into_iter().try_reduce(|x, y| x.checked_add(y)); + /// assert_eq!(sum, Some(None)); + /// ``` + /// + /// Use a [`Result`] instead of an [`Option`]: + /// + /// ``` + /// #![feature(iterator_try_reduce)] + /// + /// let numbers = vec!["1", "2", "3", "4", "5"]; + /// let max: Result, ::Err> = + /// numbers.into_iter().try_reduce(|x, y| { + /// if x.parse::()? > y.parse::()? { Ok(x) } else { Ok(y) } + /// }); + /// assert_eq!(max, Ok(Some("5"))); + /// ``` + #[inline] + #[unstable(feature = "iterator_try_reduce", reason = "new API", issue = "87053")] + fn try_reduce(&mut self, f: F) -> ChangeOutputType> + where + Self: Sized, + F: FnMut(Self::Item, Self::Item) -> R, + R: Try, + R::Residual: Residual>, + { + let first = match self.next() { + Some(i) => i, + None => return Try::from_output(None), + }; + + match self.try_fold(first, f).branch() { + ControlFlow::Break(r) => FromResidual::from_residual(r), + ControlFlow::Continue(i) => Try::from_output(Some(i)), + } + } + /// Tests if every element of the iterator matches a predicate. /// /// `all()` takes a closure that returns `true` or `false`. It applies diff --git a/library/core/tests/iter/traits/iterator.rs b/library/core/tests/iter/traits/iterator.rs index 422e389e38017..d38bca1e3b3ea 100644 --- a/library/core/tests/iter/traits/iterator.rs +++ b/library/core/tests/iter/traits/iterator.rs @@ -454,6 +454,34 @@ fn test_find_map() { } } +#[test] +fn test_try_reduce() { + let v: Vec = vec![1, 2, 3, 4, 5]; + let sum = v.into_iter().try_reduce(|x, y| x.checked_add(y)); + assert_eq!(sum, Some(Some(15))); + + let v: Vec = vec![1, 2, 3, 4, 5, usize::MAX]; + let sum = v.into_iter().try_reduce(|x, y| x.checked_add(y)); + assert_eq!(sum, None); + + let v: Vec = Vec::new(); + let sum = v.into_iter().try_reduce(|x, y| x.checked_add(y)); + assert_eq!(sum, Some(None)); + + let v = vec!["1", "2", "3", "4", "5"]; + let max = v.into_iter().try_reduce(|x, y| { + if x.parse::().ok()? > y.parse::().ok()? { Some(x) } else { Some(y) } + }); + assert_eq!(max, Some(Some("5"))); + + let v = vec!["1", "2", "3", "4", "5"]; + let max: Result, ::Err> = + v.into_iter().try_reduce(|x, y| { + if x.parse::()? > y.parse::()? { Ok(x) } else { Ok(y) } + }); + assert_eq!(max, Ok(Some("5"))); +} + #[test] fn test_iterator_len() { let v: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index d49c2552cfd4c..9ab98ba88865a 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -56,6 +56,7 @@ #![feature(iter_intersperse)] #![feature(iter_is_partitioned)] #![feature(iter_order_by)] +#![feature(iterator_try_reduce)] #![feature(const_mut_refs)] #![feature(const_pin)] #![feature(const_slice_from_raw_parts)]