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

Implement ExactSizeIterator for remaining core Iterators where applicable #21453

Merged
merged 3 commits into from
Jan 23, 2015

Conversation

Stebalien
Copy link
Contributor

Specifically:

  • Peekable
  • ByRef
  • Skip
  • Take
  • Fuse

Fixes #20547

…able.

Specifically:
 * Peekable
 * ByRef
 * Skip
 * Take
 * Fuse
@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @alexcrichton (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. The way Github handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see CONTRIBUTING.md for more information.

@Stebalien
Copy link
Contributor Author

I'd be happy to add unit tests if you want these changes. I can also break this into multiple pull requests/commits but that seemed like overkill.

#[stable]
impl<I> ExactSizeIterator for Skip<I> where I: ExactSizeIterator {
#[inline]
fn len(&self) -> uint { self.iter.len().saturating_sub(self.n) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usize (and above and below)

@Gankra
Copy link
Contributor

Gankra commented Jan 21, 2015

It would be nice to test some of these, I think. It's partially non-trivial code, especially if we're going to start using these to do "trust" iterators in unsafe code.

@Gankra
Copy link
Contributor

Gankra commented Jan 21, 2015

(I belive the tests would go in libcoretest)

@Stebalien
Copy link
Contributor Author

Quick question, should I be manually implementing len() or using the default implementation? I'm asking because the ExactSizeIterator trait actually makes guarantees about size_hint(), not len() and having both defined independently could lead to inconsistent results.

Aside: Personally, I don't think traits should impose requirements on methods of other traits (too fragile) and len() should be independent (computing an exact size might be more expensive than computing a size hint) but that should probably be (has been?) discussed elsewhere.

// value before we peeked.
self.iter.len() + if self.peeked.is_some() { 1 } else { 0 }
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can quite add this implementation just yet as I believe it's possible to return (usize::MAX, None) from this iterator (which isn't an exact size).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can because, given an underlying iterator that implements ExactSizeIterator, either:

  1. We haven't peeked: self.peeked is None and we forward the result from self.iter.size_hint() which must meet the ExactSizeIterator spec and must be at most (uint::MAX, Some(uint::MAX-1)).
  2. We have peeked: To have peeked, we must have called next() on self.iter so self.iter's size_hint() must be at most (lo, Some(hi) where lo == hi <= uint::MAX-1 because it must have met the ExactSizeIterator spec both before and after we peeked. Therefore adding 1 cannot overflow.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which must meet the ExactSizeIterator spec and must be at most (uint::MAX, Some(uint::MAX-1)).

The size_hint documentation does not mention MAX - 1 and I was not under the impression that's what it was used for. I'm curious, but where'd you get the MAX - 1 from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, that was supposed to be (uint::MAX, Some(uint::MAX)) (copy and paste error).

My basic point is that that if the number of items in an ExactSizeIterator once fit in a uint (required by the spec), it must always fit in a uint regardless of where those items are (moving one from self.iter to self.peeked doesn't change the total number of items).

@alexcrichton
Copy link
Member

Quick question, should I be manually implementing len() or using the default implementation?

I'd leave it as a default method for now personally.

@Gankra
Copy link
Contributor

Gankra commented Jan 21, 2015

Yeah I don't think there's a non-trivial len impl in the wild.

ByRef is not tested included because it is a trivial pass through.
@gereeter
Copy link
Contributor

I disagree that the adapters should use the default impl. If I write an implementation of len on an iterator I create, I expect len on ByRef to call my len function, not size_hint. Beyond just doing what is expected, this can be a bit more efficient (if, for example, inlining of size_hint doesn't happen due to it being a large function) and helps with debugging.

@@ -1791,6 +1794,9 @@ impl<T, I> Iterator for Peekable<T, I> where I: Iterator<Item=T> {
}

#[stable]
impl<T, I> ExactSizeIterator for Peekable<T, I> where I: ExactSizeIterator<Item = T> {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(copying over from previous comment to continue discussion)

Sorry, that was supposed to be (uint::MAX, Some(uint::MAX)) (copy and paste error).

My basic point is that that if the number of items in an ExactSizeIterator once fit in a uint (required by the spec), it must always fit in a uint regardless of where those items are (moving one from self.iter to self.peeked doesn't change the total number of items).

Ah right, good point! So if the underlying iterator returns (MAX, Some(MAX)) then the only time we'll add 1 is if we've already peeked in which case the underlying iterator will return (MAX-1, Some(MAX-1)).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly.

@Stebalien
Copy link
Contributor Author

I disagree that the adapters should use the default impl. If I write an implementation of len on an iterator I create, I expect len on ByRef to call my len function, not size_hint.

I agree but the spec would need to be changed. Otherwise, it would be too easy to end up with conflicting implementations of size_hint and len.

@alexcrichton
Copy link
Member

I think for now we're certainly stepping forward with these extra implementations. We may want to sort out whether delegation to len is conventional, but that should largely just be a backwards compatible change, so I'm going to send this to bors for now.

@alexcrichton
Copy link
Member

@bors: r+ 1479de8

Thanks again!

bors added a commit that referenced this pull request Jan 23, 2015
Specifically:
 * Peekable
 * ByRef
 * Skip
 * Take
 * Fuse

Fixes  #20547
@bors
Copy link
Contributor

bors commented Jan 23, 2015

⌛ Testing commit 1479de8 with merge 86fbdbf...

@bors bors merged commit 1479de8 into rust-lang:master Jan 23, 2015
@Stebalien Stebalien deleted the exactsize branch June 11, 2015 16:09
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.

Skip should implement ExactSizeIterator
6 participants