Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
rodrigocfd opened this issue Mar 27, 2024 · 7 comments
Labels
ACP-accepted API Change Proposal is accepted (seconded with no objections) api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api

Comments

@rodrigocfd
Copy link

rodrigocfd commented Mar 27, 2024

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.

@rodrigocfd rodrigocfd added api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api labels Mar 27, 2024
@the8472
Copy link
Member

the8472 commented Mar 27, 2024

A more general API would be

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

@scottmcm
Copy link
Member

Previous conversation: rust-lang/rfcs#3233 (comment)

@shepmaster
Copy link
Member

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
Copy link
Member

Amanieu commented Apr 2, 2024

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.

@Amanieu Amanieu closed this as completed Apr 2, 2024
@Amanieu Amanieu added the ACP-accepted API Change Proposal is accepted (seconded with no objections) label Apr 2, 2024
@rodrigocfd
Copy link
Author

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

@Amanieu
Copy link
Member

Amanieu commented Apr 2, 2024

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

@scottmcm
Copy link
Member

scottmcm commented Apr 3, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ACP-accepted API Change Proposal is accepted (seconded with no objections) api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api
Projects
None yet
Development

No branches or pull requests

5 participants