rust-lang / libs-team

The home of the library team
Apache License 2.0
110 stars 18 forks source link

Add `try_all`, `try_any`, `try_position` and `try_rposition` methods to `Iterator` trait #361

Closed rodrigocfd closed 3 months ago

rodrigocfd commented 3 months ago

Proposal

Problem statement

Currently there is no simple way to use all(), any(), position() and rposition() if the predicate can fail, returning an Err.

Motivating examples or use cases

This proposal came from an StackOverflow question, which asks for a fallible method for position(). The answer is rather convoluted when compared to the simplicity of try_for_each() versus for_each().

The original question is:

let items: &[Result<&str, u32>] = &[Ok("foo"), Err(444), Ok("bar")];

let bar_idx = items.iter()
    .position(|item| item? == "bar")?; // what to do here?

Solution sketch

I sketched a FooIterator with the aforementioned methods so I could use them right away, but I suppose they should be members of Iterator. Also, I'm aware the implementation below is far from being standardized:

pub trait FooIterator: Iterator {
    fn try_all<E, F>(&mut self, mut predicate: F) -> Result<bool, E>
        where Self: Sized,
            F: FnMut(Self::Item) -> Result<bool, E>,
    {
        for item in self {
            if !predicate(item)? {
                return Ok(false)
            }
        }
        Ok(true)
    }

    fn try_any<E, F>(&mut self, mut predicate: F) -> Result<bool, E>
        where Self: Sized,
            F: FnMut(Self::Item) -> Result<bool, E>,
    {
        for item in self {
            if predicate(item)? {
                return Ok(true)
            }
        }
        Ok(false)
    }

    fn try_position<E, F>(&mut self, mut predicate: F) -> Result<Option<usize>, E>
        where Self: Sized,
            F: FnMut(Self::Item) -> Result<bool, E>,
    {
        for (idx, item) in self.enumerate() {
            if predicate(item)? {
                return Ok(Some(idx));
            }
        }
        Ok(None)
    }

    fn try_rposition<E, F>(&mut self, mut predicate: F) -> Result<Option<usize>, E>
        where Self: Sized + DoubleEndedIterator,
            F: FnMut(Self::Item) -> Result<bool, E>,
    {
        for (idx, item) in self.rev().enumerate() {
            if predicate(item)? {
                return Ok(Some(idx));
            }
        }
        Ok(None)
    }
}

impl<'a, T> TryIterator for core::slice::Iter<'a, T> {}
impl<I> TryIterator for std::iter::Enumerate<I> where I: Iterator {}
impl<I> TryIterator for std::iter::Skip<I> where I: Iterator {}
impl<I> TryIterator for std::iter::StepBy<I> where I: Iterator {}
impl<I> TryIterator for std::iter::Take<I> where I: Iterator {}

Alternatives

I implemented and published the TryIterator crate, so I could use these methods immediately. But I believe these methods have their place in the standard library.

the8472 commented 3 months ago

A more general API would be

let position: Result<usize, _> =  iter.try_and(|it| it.position(|e| e == "bar"));
scottmcm commented 3 months ago

Previous conversation: https://github.com/rust-lang/rfcs/pull/3233#discussion_r815183078

shepmaster commented 3 months ago

itertools::process_results / Itertools::process_results are offshoots of the original shunt code that I added to the standard library. Both versions have evolved since then, but presumably still solve the same core issue.

use itertools::Itertools; // 0.12.1

fn main() {
    let v = [Ok::<u8, ()>(1), Ok(3)]
        .into_iter()
        .process_results(|mut i| i.all(|i| i > 2));
    assert_eq!(Ok(false), v);

    let v = [Ok::<u8, ()>(1), Ok(3)]
        .into_iter()
        .process_results(|mut i| i.position(|i| i > 2));
    assert_eq!(Ok(Some(1)), v);
}
Amanieu commented 3 months ago

We discussed this in the libs-api meeting and we are happy to add these. However these should be based on the Try trait to allow them to work with both Option and Result. Additionally, the implementation should use try_fold internally since many iterators will specialize that method for better performance. Finally, stabilization of these will likely be blocked on the stabilization of the Try trait, like most other methods using that trait.

Feel free to open a tracking issue and open a PR to rust-lang/rust to add it as an unstable feature.

rodrigocfd commented 3 months ago

@Amanieu where I can find information about this Try trait?

Amanieu commented 3 months ago

Have a look at the existing try_find and try_reduce methods on the Iterator trait.

scottmcm commented 3 months ago

Finally, stabilization of these will likely be blocked on the stabilization of the Try trait, like most other methods using that trait.

Note that try_all and try_any will be blocked only on Try, but try_(r)position will also be blocked on Residual, like how try_find is blocked on it.