Access iterator without advancing it

When iterating over a collection, and breaking this iteration into different parts (for whatever reason), is it possible to get access to the element in the collection again without advancing the iterator by calling next?

fn main() {
    let mut b : [u8; 4] = [1,2,3,4];
    let mut it = b.iter_mut().enumerate();

    while let Some((i, x)) = it.next() {
        println!("i={} x={}",i,x);
        *x += 1;

        if i == 1 { break; }
    }

    // is it possible to get the last (i,x) again here?
    // i.e. without advancing to the next element?
    // or at least get the position that enumerate yielded?

    while let Some((i, x)) = it.next() {
        println!("i={} x={}",i,x);
        *x += 10;
    }

    println!("{:?}", b);
}

In C++, one could just re-use *it, like so:

#include <vector>
#include <iostream>

int main() {
  std::vector<int> vec = { 1, 2, 3, 4 };

  auto it = vec.begin();

  int pos = 0; // to emulate the enumerate without using boost or the like
  while (it != vec.end()) {
    *it += 1;
    if (pos++ == 1)
      break;
    ++it;
  }

  *it += 10; // re-access the iterator without moving forward
  ++it;      // of course one should check whether this is already the end

  while (it != vec.end()) {
    *it += 10;
    ++it;
  }

  for (const auto v : vec) {
    std::cerr << v << " ";
  }
  std::cerr << "\n";

  return 0;
}

In the rust case, I would mostly be interested in reading the index produced by enumerate, but outside the while and without advancing the iterator.

You could use peekable to create an iterator where you can use peek or peek_mut to look one element into the future. If you use that in your first loop, you can access the last element seen by it in the second loop:

fn main() {
    let mut b: [u8; 4] = [1, 2, 3, 4];
    let mut it = b.iter_mut().enumerate().peekable();

    while let Some((i, x)) = it.peek_mut() {
        println!("loop 1: i={} x={}", i, x);
        **x += 1;

        if *i == 1 {
            break;
        }
        it.next();
    }

    // is it possible to get the last (i,x) again here?
    // i.e. without advancing to the next element?
    // or at least get the position that enumerate yielded?

    while let Some((i, x)) = it.next() {
        println!("loop 2: i={} x={}", i, x);
        *x += 10;
    }

    println!("{:?}", b);
}

Playground.

8 Likes

Your code wouldn't work with just any random C++ iterator. It doesn't work with istream_iterator<int>, e.g.

For better or for worse Rust uses radically different approach to generics than C++: while in C++ template may be implemented radically differently for different types in Rust trait specifies the interface (like C++ concepts and you couldn't use anything that's not part of that interface).

If you want something else you'll need another interface.

Not every iterator may naturally offer you the ability that you are seeking, that's why in Rust you need to do something extra to do that. C++ istream_iterator<int> does, but only because it implements something like aforementioned peekable inside which means that you pay for that ability even if you don't use it, contrary to C++ motto.

Of course there are no such thing as a free lunch, in Rust you may end up playing for the ability to peek even if you use it with iterators that can offer that ability naturally… but compiler often (but not always) can optimize away that cost.

2 Likes

Depends on the iterator. Some implement Clone cheaply, usually by-reference iterators, so you can iterate it many times by cloning it.
A few Iterators have an as_slice() method or implement AsRef<[T]>.

1 Like

What would be nice is to get at least the number produced by "enumerate" without moving the iterator. So that would be "peeking" at just the enumerate counter, not the item the iterator produces.

You can always store it yourself:

// Make an iterator that stores the enumerate count automatically 
let last_idx = Cell::new(0_usize);
let mut it = b.iter_mut().enumerate().inspect(|&(i,_)| last_idx.set(i));

// Get the last index produced
let i = last_idx.get();

Playground example — Maybe doesn’t produce exactly the output you’re looking for. Without consuming the iterator, I didn’t know what you wanted to do with the second loop.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.