-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: RewindIterator for bidirectional iterators #2896
Conversation
You could do this with fn are_all_unique(slice: &[i32]) -> bool {
let mut iter = slice.iter();
while let Some(item) = iter.next() {
let rest = iter.clone();
if rest.find(|x| x == item) {
return false
}
}
true
} This works with all std iterators over collections that return references, and even with combinators provided that the closures/iterators passed into the combinators are also Using clone has the added benefit that it usually is just copying some pointers, so it's really cheap. However, rewinding to a previous state can get really expensive. I was able to convert your edit:
Note: if an iterator yields exclusive references, then it is unsound for it to implement Now, for owning iterator, it may be expensive to clone them, but that's a cost that must be paid regardless. I think your idea of snapshot iterators is interesting, and could mitigate this cost.
You could build the idea of a snapshot iterator on top of the current implementation, for example a impl<'a, T> std::slice::IterMut<'a, T> {
fn snapshot(&self) -> std::slice::Iter<'_, T> { ... }
} edit 2: I guess the snapshot method above could be approximated as |
I do not believe that that is true. Since the returned mutable references are tied to the lifetime of the mutable reference to the iterator, the borrow checker would catch any attempt to continue using the values yielded by previous calls to the iterator after calling I agree with you that
I don't think owning iterators will be able to implement
Yes, you can implement it for a specific type (i.e., without a trait) just fine today. But you cannot (I think) make a trait for it, since that would require generic associated types as mentioned in the RFC. |
@KrishnaSannasi The use of A simple litmus test would be to implement a Turing machine, where |
What I'm talking about is something like let first = iter.next().unwrap();
let second = iter.previous().unwrap();
let third = iter.next().unwrap(); Here either
No, it would be unsound for mutable iterators to implement
I think you could do it with a trait like this: trait SnapshotIterator<'a>: Iterator {
type Snapshot: 'a;
fn snapshot(&'a self) -> Self::Snapshot;
} But this has worse ergonomics than GATs
I didn't fully understand what @frankmcsherry was suggesting before I previously commented. Yes, a turing machine would be difficult to implement using just |
@frankmcsherry please also note that rewind is likely more expensive than |
This isn't true. See this playground for an example; Rust iterators are not "streaming iterators," so yielded references must live for the lifetime of the iterator, not just until the next So without streaming iterators (read: GAT), you can't have a rewindable iterators of unique references. You could maybe do it by the signature On top of that, this would require the size of So if you really want this, it'd have to be
What I think you want is something more like (EDIT: sorry about the multiple posts, the GH app was misbehaving.) |
Ah, damn, you're right. In my head, I had it that the return type of
Yeah, I think you're right. You've convinced me it's sadly probably not possible to model this on top of Thanks for the quick feedback! PS: @CAD97 it looks like your comment got duplicated with the GitHub stuttering going on atm :) |
Rendered
Today, we have
std::iter::Peekable
, which allows a single-element look-ahead in iterators.Peekable
is useful, but it must buffer one element of the iterator, since it has now knowledge of the underlying iterator implementation. It also only allows peeking at a single element, and only provides a reference to it at that. This means that if you peek ahead in an iterator that provides mutable access, you cannot mutate the peeked-at element.Some iterators can provide much more robust navigation options for the iterator, and can do so efficiently. The most immediately useful of these is the ability to rewind the iterator: effectively undoing the last call to
Iterator::next
. An iterator that supports rewinding allows the user to move both forwards and backwards through the underlying element stream. This in turn allows peeking all the way to the end of the iterator if need be, and allows iterating over the same set of elements more than once.Beyond peeking, the most obvious use-cases for rewinding is for iterators where consecutive elements are related in some fashion. For example, for a
BTreeMap<(Game, Team, Player), usize>
, the user may wish to compute aggregate statistics for each team for a game, but then also compute something per player that depends on the team statistics. Currently, developers are forced to either do multiple lookups into the map, or iterate over the whole map multiple times. With a rewinding iterator, neither is necessary; the developer can simply rewind the iterator after computing the team statistics, and then iterate again to enumerate the players.