Let's leave the naming issue to one side. Stepanov defines a hierarchy of iterators based on the abstract algebra of data access patterns. The key insight, and the foundation of generic programming is to study the algorithm not the data structure. Algorithms get classified by their data-access requirements, and iterators turn those classifications into APIs.
So lets look at the algorithm, it requires access to two different locations at the same time. This brings us to the semantic difference between a plain "Iterator" and a "ForwardIterator". A plain Iterator models something like a stream, you cannot go backwards, and you cannot hold a reference to any element except the current element (because the underlying stream advances on call to 'next'), technically this means the successor fuction of an Iterator is not a "Regular Procedure/Function' (It does not always give the same output for the same input), A ForwardIterator is multi-pass, you can go through the collection multiple times, and that means you can copy the iterator and it's successor function is a regular procedure.
Does Rust distinguish between Iterators and ForwardIterators? If it does not it is not capable of expressing the semantic difference between these two cases. This is important because the algorithms for a ForwardIterator are different from those for a plain Iterator. To use some code from my Rust translation of EoP to illustrate, here is the "find_adjacent_mismatch" algorithm, a simpler algorithm with the same requirement to 'peek' at the current elements neighbour. Probably the important bits to read this code are that 'next' is split into:
fn successor(self) -> Self // from Iterator trait
fn source(&self) -> &Self::ValueType' // from Readable trait
However I don't think these differences change the fundamental point, and the distinction between plain Iterators and ForwardIterators could be made with Rust style iterators.
pub fn find_adjacent_mismatch<I, R>(mut f : I, l : &I, mut r : R) -> I
where I : Iterator + Readable, R : FnMut(&I::ValueType, &I::ValueType) -> bool {
// Precondition: readable_bounded_range(f, l)
if f != *l {
let mut x = (*f.source()).clone();
f = f.successor();
while (f != *l) && r(&x, f.source()) {
x = (*f.source()).clone();
f = f.successor();
}
}
f
}
The above is for a plain Iterator (using Stepanov style iterators, you can find the definitions in the rust translation of EoP above if you are interested). Note, it has to clone the element because it cannot copy the iterator (and Rust allows this semantics to be enforced, the Iterator is neither 'Copy' nor 'Clone' and 'successor' consumes the old iterator to produce the next one). Contrast with:
pub fn find_adjacent_mismatch_forward<I, R>(mut f : I, l : &I, mut r : R) -> I
where I : ForwardIterator + Readable, R : FnMut(&I::ValueType, &I::ValueType) -> bool {
// Precondition: readable_bounded_range(f, l)
if f != *l {
let mut t = f.clone();
f = f.successor();
while f != *l && r(t.source(), f.source()) {
t = t.successor();
f = f.successor();
}
}
f
}
This is a different algorithm, that requires a ForwardIterator because it needs to copy (Clone) the iterator in order to access the previous element.
So in general there is no solution to this problem, unless you distinguish between plain Iterators and ForwardIterators, otherwise if you allow iterators to be copied/cloned some algorithms will go-wrong on some collections (streams / channels / event-queues etc), or you are restricted to only one class of algorithms (the algorithms definable on plain iterators). Also it is worth noting that a BidirectionalIterator is by necessity a ForwardIterator, so it looks to me like Rust is missing a class of iterator between the plain and the bidirectional. Rather than just creating iterators ad-hoc, it is better to study the classes of algorithms and define iterators for those classifications. Fortunately Stepanov has already done this for us.
Edit: Unfortunately a Rust DoubleEndedIterator is not semantically the same as a bidirectional iterator. A bidirectional iterator can go left/right/left etc, it does not consume input as such. This makes it clear there is a real semantic difference between what rust calls an iterator and what Stepanov calls an iterator. Rust iterators always consume something, whereas only Stepanov's plain Iterator models consumption. As far as I can see the Rust standard library has no answer for the higher iterator concepts, and this severely limits the generic algorithms that can be written. However to have the generic power of higher iterator concepts appears to require the sacrifice of some safety (IE you can make an iterator go out of bounds if you make a mistake). I have some ideas about a solution, but that's for a different thread.