Getting current item of iterator

Hi,
I want like to get the current item of an iterator where it points to.
I.e., in C++ I can increment an iterator with ++it and can retrieve the current value with *it. I couldn't find something similar in Rust.

How can I get the current item without calling iterator.rev() and then iterator.next()?

fn parse_string(&mut self, iterator: &mut Bytes) {
        loop {
            match iterator.next() {
                Some(char) if char == b'"' => {
                    break;
                },
                // Do something else...
            }
        }

        // Pseudo code!!!
        if let Some(current_char) = iterator.current_item() {
             // Do something
        } else {
            // Iterator has reached end. Do something else!
        }
    }

Thank you in advance!

1 Like

In Rust we either store the result of calling next() in a local variable (or this is done for us by using a for loop), or use a peekable iterator in cases where this isn't sufficient. There is a recent thread on the same topic.

1 Like

This was literally asked and answered 3 days ago.

Thank you, but peekable isn't exactly what I want, because I have to call peek() and then next() on different match cases. This would be error-prone and ugly.
And I also wanted to avoid having a local variable, but I seems I have to do this.
Strange, that such a basic thing isn't in the API and it seems to be a recurring topic...

Anyway, thank you for your replies.

If you can give a specific example where iterators and peekable are inconvenient, you'll get a whole bunch of suggestions. But in the code you posted, a local variable the most obvious solution.

1 Like

This would be my peekable solution:

fn parse_string(&mut self, source: &str, iterator: &mut Bytes) {
        let mut peekable = iterator.peekable();
        loop {
            match peekable.peek() {
                Some(char) if char == &b'"' => {
                    //Matches second quote
                    break;
                },
                Some(b'\n') => {
                    self.line += 1;
                    let _ = peekable.next();
                }
                _ => {
                    // String content
                    let _ = peekable.next();
                }
            }
        }

        if None == iterator.next() {
            // Iterator has reached end. Do something else!
            self.error(self.line, "Unterminated string.");
            return;
        }

        // ...
    }

vs. a solution with a current_element()

fn parse_string(&mut self, source: &str, iterator: &mut Bytes) {
        loop {
            match iterator.next() {
                Some(char) if char == b'"' => {
                    //Matches second quote
                    break;
                },
                Some(b'\n') => self.line += 1,
                _ => {
                    // String content
                }
            }
        }

        if None == iterator.current_element() {
            // Iterator has reached end. Do something else!
            self.error(self.line, "Unterminated string.");
            return;
        }
    }

In your peekable version it looks like you swapped your uses of next for peeks for some reason. A peekable iterator doesn't change the meaning of next, so don't do that. Instead, use peek where you would have used current_element.

-        if None == iterator.current_element() {
+        if iterator.peek().is_none() {
4 Likes

Rust iterators don't describe a collection. They are a single-use-only stream of objects.

There may not be a current element, because elements can be computed on the fly, by an arbitrarily complex pipeline that is allowed to have side effects. Iterators may also return objects with exclusive access or unique ownership, so they can't safely hold on to the last object returned.

Peekable iterator reads one element ahead of time (which is an observable side effect), buffers it, and gives temporary access to the buffer.

9 Likes

Nit: don't forget to break the loop when None is returned.

1 Like

That's not the solution, as peek() peeks the next element. It is like array[index+1], but I want to know array[index].

This code is not working as you are suggesting.

You are not looking for the "current" element then, but the "last yielded" element. However since it was already yielded the iterator does not have ownership of it anymore, so it won't be able to give it to you.

5 Likes

Well, I clearly compared it with C++ iterator. *it yields the current element. It is a dereference of the current pointer. peek() would be *(it+1) which is something different.

Okay, then I got it right, that this is just not possible with Rust.

It’s not as convenient with Rust’s std::iter::Iterator trait, for sure.

For parsers, I often find it useful to make my own iterator-like types that include “current item” and other lookahead methods. You can see an example here: https://limpet.net/mbrubeck/2014/08/11/toy-layout-engine-2.html

Or, for what it’s worth, here’s a version of your code that uses the “save the last element in a local variable” approach:

fn parse_string(&mut self, _source: &str, iterator: &mut Bytes) {
    let mut current_elem;
    loop {
        current_elem = iterator.next();
        match current_elem {
            Some(b'"') => break, // Matches second quote
            Some(b'\n') => self.line += 1,
            Some(_) => continue, // String content
            None => break,
        }
    }

    if current_elem.is_none() {
        // Iterator has reached end. Do something else!
        self.error(self.line, "Unterminated string.");
        return;
    }
    // ...
}

Of course for this particular case you could also do:

fn parse_string(&mut self, _source: &str, iterator: &mut Bytes) {
    loop {
        match iterator.next() {
            Some(b'"') => break, // Matches second quote
            Some(b'\n') => self.line += 1,
            Some(_) => continue, // String content
            None => {
                // Iterator has reached end. Do something else!
                self.error(self.line, "Unterminated string.");
                return;
            }
        }
    }
    // ...
}
4 Likes

Local variables (as in what @mbrubeck posted) work well here and the code is very clear (to me), in spite of being slightly verbose.

But if the problem is actually just to determine how the loop ended (whether the string was terminated), you can break the loop with a value.

1 Like

Boy, are you overcomplicating this whole thing!

You don't even need peekable. It can be done in 12 lines:

fn parse_string(&mut self, source: &str, iterator: &mut Bytes) {
    for byte in iterator {
        match byte {
            b'\n' => {
                self.line += 1;
            }
            '"' => return,
            _ => { /* string content */ }
        }
    }
    self.error(self.line, "Unterminated string.");
}

By the way, if you are parsing strings, you absolutely, positively, 100% surely don't want bytes.

5 Likes

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.