Implement Iterator/IntoIterrator properly

I am quite new to Rust and want to clear some basic doubt about Iterators.

Iterator is implemented for a simple Counter struct. I assume this also causes IntoIterator to also be implemented for Counter. However I want to implement IntoIterator for &Counter.

What should IntoIterator return in this case? A new Iterator type or Counter?

Edit: Link fixed.

One way you can make this work is by wrapping the inner value in a Cell, but this allows behavior that may be surprising to most Rust programmers: In most cases, you can rely on a for loop to not skip any elements because it has exclusive access to the iterator. But this doesn't have that property: Because it uses interior mutability, it's possible to advance the iterator while the loop is running.

use std::cell::Cell;
struct Counter(Cell<usize>);

impl Counter {
    pub fn get_last_value(&self) -> usize {
        self.0.get()
    }
    
    pub fn new(init:usize)->Self {
        Counter(Cell::new(init))
    }
}

impl Iterator for &Counter {
    type Item = usize;
    
    fn next(&mut self) -> Option<Self::Item> {
        let v = self.0.get();
        if v < 20 {
            self.0.set(v+1);
            Some(v)
        } else {
            None
        }
    }
}

fn main()
{
    let c = Counter::new(10);
    
    // Looping this way works as 'c' is borrowed by Iterator::next()
    //while let Some(i) = c.next() {
    //    println!("{i}");
    //    println!("Remaining: {}", c.get_last_value());
    //}
    
    for i in &c {
        println!("{i}");
        println!("Remaining: {}", c.get_last_value());
    }
}
2 Likes

Thanks. Can you explain if its possible to implement Iterator over shared reference of a type without interior mutability.

Only in the most trivial cases (a never-ending iterator that always yields the same thing, for example). To be useful, an Iterator implementation has to change some kind of internal state when you call next(). Without internal mutability, there's no place for that state to live inside &Something.

There may be some confusion caused by the fact that you're calling a method on the iterator, get_last_value, in addition to iterating the values.

When using a for loop, you don't have access to the Iterator itself (Counter), so you can't call Counter::get_last_value. This is because the iterator itself is moved or "consumed" by the into_iter method. Note that this method takes the self parameter by value, not by reference, and this is what causes it to be moved:

fn into_iter(self) -> Self::IntoIter

So you have to use a while loop instead (as you did), if you need to call methods on the iterator itself.

It may help to look at one of the std library iterators that also has methods on the iterator. The iterator for slices, Iter has an as_slice method that returns the remaining portion of the original slice. This method can be called from a while loop, but not a for loop.

fn main() {
    let s = &[1, 2, 3];
    
    let mut s_iter = s.iter();
    while let Some(i) = s_iter.next() {
        println!("{}, remaining: {:?}", i, s_iter.as_slice());
    }
    
    for i in s {
        println!("{}", i);
        // there is no way to call as_slice()
    }
}
3 Likes

Thanks. This is exactly what I was confused about. So apart from using internal mutability wrappers (which works but might not be the usual way), is having a separate wrapper type (say CounterInterator) for iterating over Counter the proper/idiomatic way?

Yes, assuming that Counter is the data you want to iterate and potentially keep around after iteration. In other words, Counter would be in the role of a slice in the std library example I posted.

1 Like

For the OP I'd probably just do this (without knowing more about the motivation anyway).

    let mut c = Counter(10);
    let iter = &mut c;
    while let Some(i) = iter.next() {
        println!("{i}");
        println!("Remaining: {}", iter.get_last_value());
    }

(And if that isn't suitable, I agree the next step is probably a separate CounterIterator type.)

That's what they did do in their playground, in the commented out code, but then they tried a for loop unsuccessfully (of course):

    // Looping this way works as 'c' is borrowed by Iterator::next()
    //while let Some(i) = c.next() {
    //    println!("{i}");
    //    println!("Remaining: {}", c.get_last_value());
    //}
    // Looping this way fails since 'c' is moved by IntoIter::into_iter()
    for i in c {
        println!("{i}");
        println!("Remaining: {}", c.get_last_value());
    }
1 Like

Ah, I missed the comment / missed the point I suppose.

1 Like