Skip to content

Conversation

stepancheg
Copy link
Contributor

Useful because inner iterator may have count() specialized.

Copy link

codecov bot commented Aug 3, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.01%. Comparing base (6814180) to head (c54568c).
⚠️ Report is 155 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1046      +/-   ##
==========================================
- Coverage   94.38%   94.01%   -0.37%     
==========================================
  Files          48       50       +2     
  Lines        6665     6254     -411     
==========================================
- Hits         6291     5880     -411     
  Misses        374      374              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@phimuemue
Copy link
Member

Hi there, thanks for the idea.

However, I'm pondering whether

fn exactly_one(mut self) -> Result<Self::Item, ExactlyOneError<Self>>

should rather be

fn exactly_one(&mut self) -> Result<Self::Item, ExactlyOneError<Self>>

and ExactlyOneError should contain an Option<[Self::Item; 2]>, so that the user can inspect the first two elements, and simply re-use the iterator afterwards, if they want to. (Similar to our all_equal_value.)

I think this is better, because at the moment we must specialize each and every function/trait (e.g. ExactSizeIterator, DoubleEndedIterator) to exploit the underlying iterator's specializations.

With my proposal, the user could still do this:

match iter.exactly_one() {
 Ok(value) => do_stuff_with(value),
 Err(None) => iterator_was_empty(),
 Err(Some(values)) => {
  // process *all* elements of the original iterator:
  values.chain(iter).for_each(...);
 }
}

or - terse -:

match iter.exactly_one() {
 Ok(value) => do_stuff_with(value),
 Err(optional_2_values) => {
  // process *all* elements of the original iterator:
  optional_2_values.into_iter().flatten().chain(iter).for_each(...);
 }
}

That is, we do not lose functionality, but the callers profit all specializations given by std.

Note: From my experience, re-visiting all elements of the original iterator is very rare. In most cases, it only matters if exactly_one worked or not, and if it did not work, the elements themselves are not relevant.

@phimuemue phimuemue added this pull request to the merge queue Aug 3, 2025
Merged via the queue into rust-itertools:master with commit 50d01b4 Aug 3, 2025
12 of 14 checks passed
@stepancheg
Copy link
Contributor Author

I like current API because it is ergonomic, and reconstructing iterator with chain calls is not.

If we change exactly_one to accept &mut self, we can do this:

fn exactly_one<'a>(&'a mut self) -> Result<Self::Item, ExactlyOneError<'a, Self>>

struct ExactlyOneError<'a, I> {
    first_two: Option<Either<[I::Item; 2], I::Item>>,
    inner: &'a mut I,
}

this way we allow recovery original iterator, but

  • still require specialization of ExactlyOneError iterator
  • ExactlyOneError is now borrowed, so it cannot be as easily passed by value

So I'd prefer to keep it as is. And if we have to specialize a hundred operations of iterator, this should be fine, as itertools is quite popular library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants