Iterators in Rust and C++ are conceptually quite different.
C++
In C++, an iterator is like a pointer. Iterators refer to an object, they can be incremented to refer to the next object, and they can be compared for equality with other iterators. Iterators can also refer to no object at all—they can refer to the “one past the end” element of a sequence, or they can be “singular” (which is like a null pointer). Some iterators support additional operations like moving both forwards and backwards, random access, and being copied.
A pointer in C++ is a valid iterator, but there are also other types which are iterators.
Iterators do not represent a sequence of elements, at least, that is not the convention. In C++, if you want a sequence of elements, you need a pair of iterators*: one for the beginning and one for the end. You aren't forced to iterate over the elements in sequence, you can do all sorts of other things. For example, if you want to reverse an array in C++, you can do it with iterators:
#include <algorithm>
#include <iterator>
#include <cstdio>
#include <utility>
template <typename T, std::size_t N>
void reverse_array(T (&arr)[N]) {
using std::swap;
auto left = std::begin(arr), right = std::end(arr);
while (left < right) {
--right;
swap(*left, *right);
++left;
}
}
int main() {
int x[] = {1, 2, 3, 4, 5};
reverse_array(x);
for (const auto it : x) {
std::printf("%d\n", it);
}
return 0;
}
But you can quickly generalize it to work on any container with bidirectional iterators:
#include <algorithm>
#include <iterator>
#include <list>
#include <cstdio>
#include <utility>
template <typename Iterator>
void reverse_any(Iterator left, Iterator right) {
using std::swap;
while (left != right) {
--right;
if (left == right)
break;
swap(*left, *right);
++left;
}
}
int main() {
std::list<int> list{1, 2, 3, 4, 5};
reverse_any(std::begin(list), std::end(list));
for (const auto it : list) {
std::printf("%d\n", it);
}
return 0;
}
Rust
In Rust, an iterator is like a slice. Iterators refer to a sequence of objects, and elements can be accessed from the iterator using the next()
method. In a sense, this means an iterator in Rust has both the begin
and end
iterator inside it†. Reimplementing the C++ code above in Rust, you'll get something like this:
fn reverse_any<'a, T: 'a, Iter>(mut iter: Iter)
where
Iter: DoubleEndedIterator<Item = &'a mut T>,
{
while let Some(left) = iter.next() {
if let Some(right) = iter.next_back() {
std::mem::swap(left, right);
}
}
}
fn main() {
let mut v = [1, 2, 3, 4, 5];
reverse_any(v.iter_mut());
println!("{:?}", v);
}
This has the added benefit of safety. Iterator invalidation is one of the most common sources of errors in C++ programs, but Rust eliminates the problem completely.
The cost is that if you want to mutate the elements, you are limited to a single (possibly double-ended) iterator in Rust, while in C++, you can have as many iterators as you want working with the same container. While single-ended and double-ended ranges are the most common case for iterators, there are some algorithms that use the additional flexibility that C++ provides.
One simple example I can think of is C++'s std::remove_if
. A straightforward implementation of remove_if
would use three iterators: two iterators for keeping track of the range of elements being scanned, and a third iterator for tracking the elements being written. You could translate std::remove_if
to Rust, but it would not be able to operate on normal Rust iterators and still modify the container in-place.
Another simple example is the Dutch National Flag problem, which commonly uses three iterators. The solution to this problem is often used for partitioning elements for quicksort, so it's an important problem.
Summary
A Rust iterator is almost equivalent to a start + end C++ iterator pair. C++ allows you to use multiple iterators and move iterators forwards and backwards. Rust guarantees that you don't accidentally use an invalid iterator, but you can only use one at a time and it can only move in one direction.
I don't know of any terminology for distinguishing these types of iterators. Note that Rust-style iterators are much more common, iterators in C#, Python, Java, etc. work the same way but they might have slightly different names (they are called "enumerators" in C#).
Footnotes
*: Technically this is not true. You only need to have one iterator in C++, however, it is conventional to have a pair and library functions typically operate on pairs of iterators (so you "need" two iterators if you want to use those functions). The fact that you have a (start,end) pair does not mean that sequences are bounded, the end iterator can be infinitely far away. Think of it like having a range (0,∞) in mathematics... ∞ isn't really a number, it's just a placeholder that lets you know that the range is unbounded on the right.
†: Remember that just because the “end” iterator exists in C++ does not mean that the sequence actually has an end. Some end iterators in C++ are like infinity. They don’t point to valid elements, and no matter how many times you iterate forward, you won’t reach infinity. In Rust, the equivalent construction is an iterator that never returns None
.