Search code examples
rustiteratorlexer

while loops that advance an iterator - is this good practise?


I am writing a top-down parser in rust from scratch, this is what i have so far.

use std::str::Chars;

struct Lexer<'a> {
    peek: Option<char>,
    chars: Chars<'a>,
}

impl<'a> Lexer<'a> {
    fn new(string: &'a str) -> Self {
        let mut chars = string.chars();
        Self {
            peek: chars.next(),
            chars
        }
    }

    fn next(&mut self) -> Option<char> {
        let peek = self.peek;
        self.peek = self.chars.next();
        peek
    }

    fn peek(&self) -> Option<char> {
        self.peek
    }

    fn next_if<F>(&mut self, cond: F) -> Option<char> where
        F: FnOnce(char) -> bool {
    
        if cond(self.peek()?) {
            self.next()
        } else {
            None
        }
    }

    fn eat_while<F>(&mut self, cond: F) where
        F: FnOnce(char) -> bool {

        while self.next_if(cond).is_some() {}
    }
}

here you can see my eat_while implementation takes in a lambda (called cond) and advances the iterator until the specified value doesn't match cond, or the chars iterator is empty. because the while loop advances the iterator, there is no need for a loop body. however, i am told an empty while loop is bad practise

what i tried instead is recreating the "next_if" functionality in eat_while.
however, this appears to be impossible:

while cond(self.peek()).is_some()
// does not work

because self.peek() returns an option, there is no way to pass it into cond.
i tried unwrapping self.peek():

while cond(self.peek().unwrap()).is_some()
// does not work

this will panic if you reach the end of the file, so it is DEFINITELY not an option.


Solution

  • You can use Option::and_then in that case:

    while self.peek().and_then(|p| cond(p)).is_some() {/*...*/}