Optionally call `skip` in a custom iterator `next()` function

Viewed 342

I have a custom iterator and I would like to optionally call .skip(...) in the custom .next() method. However, I get a type error because Skip != Iterator.

Sample code is as follows:

struct CrossingIter<'a, T> {
    index: usize,
    iter: std::slice::Iter<'a, T>,
}

impl<'a, T: Float> Iterator for CrossingIter<'a, T> {
    type Item = (usize, T);


    fn next(&mut self) -> Option<(usize, T)> {
        let iter = (&mut self.iter).enumerate();
         
        let iter = if self.index == 0 {
            self.index += 3;
            iter.skip(3)
        } else {
            iter
        }

        // lots of code here working with the new iterator        

        iter.next()
    }
}

The issue is that after calling .skip(3), the type of iter has changed. One solution would be to duplicate the // lots of code ... in each branch of the if statement, but I'd rather not.

My question is: Is there a way to conditionally apply skip(...) to an iterator and continue working with it without duplicating a bunch of code?

2 Answers

skip is designed to construct a new iterator, which is very useful in situations where you want your code to remain, at least on the surface, immutable. However, in your case, you want to advance the existing iterator while still leaving it valid.

There is advance_by which does what you want, but it's Nightly so it won't run on Stable Rust.

if self.index == 0 {
  self.index += 3;
  self.iter.advance_by(3);
}

We can abuse nth to get what we want, but it's not very idiomatic.

if self.index == 0 {
  self.index += 3;
  self.iter.nth(2);
}

If I saw that code in production, I'd be quite puzzled.

The simplest and not terribly satisfying answer is to just reimplement advance_by as a helper function. The source is available and pretty easy to adapt

fn my_advance_by(iter: &mut impl Iterator, n: usize) -> Result<(), usize> {
  for i in 0..n {
    iter.next().ok_or(i)?;
  }
  Ok(())
}

All this being said, if your use case is actually just to skip the first three elements, all you need is to start with the skip call and assume your iterator is always Skip

struct CrossingIter<'a, T> {
  index: usize,
  iter: std::iter::Skip<std::slice::Iter<'a, T>>,
}

I think @Silvio's answer is a better perspective.


You may call skip(0) instead of the iter itself in else branch...

And the return value of the iterator generated by enumerate doesn't match your definition: fn next(&mut self) -> Option<(usize, T)>. You need to map it.

Here is a working example:

use num::Float;

struct CrossingIter<'a, T> {
    index: usize,
    iter: std::slice::Iter<'a, T>,
}

impl<'a, T: Float> Iterator for CrossingIter<'a, T> {
    type Item = (usize, T);


    fn next(&mut self) -> Option<(usize, T)> {
        let iter = (&mut self.iter).enumerate();
         
        let mut iter = if self.index == 0 {
            self.index += 3;
            iter.skip(3)
        } else {
            iter.skip(0)
        };

        // lots of code here working with the new iterator        

        iter.next().map(|(i, &v)| (i, v))
    }
}
Related