I have a lexer (program that turns source code into tokens) written in Go that I am re-writing in Rust.

The lexer should take in a sequence of tokens, generally different special characters, integers, or string sequences (identifiers) and output tokens representing those 'types'.

I'd like to write more idiomatic Rust, using non-mutable data wherever possible, but the current 'design' is akin to OOP with a struct mutating itself in most methods. I am struggling to come up with a better design that is more idiomatic Rust, so input would be highly appreciated.

As you can see in the code below, the `Lexer` struct holds different position counters that are mutated in most methods, meaning that the entire struct is passed as mutable. This seems to be a specific code smell to me, and I would like to refactor that out if nothing else.

```rust
use crate::token::{match_keyword, Token};

/// A lexer contains a vector of characters as well as various pointers to read and peek characters
/// in the input.
pub struct Lexer {
    input: Vec<char>,
    position: usize,      // current reading position
    peek_position: usize, // current peeking position
    ch: char,             // current char under examination
}

impl Lexer {
    pub fn new(input: String) -> Self {
        let in_vec: Vec<char> = input.chars().collect();
        let c = in_vec[0];
        Self {
            input: in_vec,
            position: 0,
            peek_position: 1,
            ch: c,
        }
    }

    pub fn next_token(&mut self) -> Option<Token> {
        self.skip_whitespace();

        let tok = match self.ch {
            '+' => Token::PLUS,
            '-' => Token::MINUS,
            '*' => Token::ASTERISK,
            '!' => {
                if self.peek_char()? == '=' {
                    self.read_char(); // advance past '='
                    Token::NEQ
                } else {
                    Token::BANG
                }
            }
            '?' => Token::QUESTION,
            '>' => Token::GT,
            '<' => Token::LT,
            ',' => Token::COMMA,
            ';' => Token::SEMICOLON,
            '(' => Token::LPAREN,
            ')' => Token::RPAREN,
            '{' => Token::LBRACE,
            '}' => Token::RBRACE,
            '|' => {
                if self.peek_char()? == '>' {
                    self.read_char();
                    Token::PIPE
                } else if self.peek_char()? == '|' {
                    self.read_char();
                    Token::LOR
                } else {
                    // FIXME is there a way around this?
                    Token::ILLEGAL(String::from(self.ch))
                }
            }
            '/' => {
                // TODO add comment support aka skip anything from double // to newline
                if self.peek_char()? == '/' {
                    // need to skip to newline
                    while self.ch != '\n' {
                        self.read_char();
                    }
                    Token::COMMENT // this will be discarded in parsing. maybe keep it for reflection
                } else {
                    Token::SLASH
                }
            }
            '"' => {
                while self.peek_char()? != '"' {
                    self.peek_position += 1;
                }
                self.position = self.peek_position; // catch current pos up to the peek
                Token::STRING(
                    self.input[self.position..self.peek_position]
                        .into_iter()
                        .collect(),
                )
            }

            _ => {
                if self.ch.is_alphabetic() {
                    let mut lit = String::from(self.read_char());
                    while self.input.get(self.position)?.is_alphabetic() {
                        lit.push(self.read_char());
                    }
                    match_keyword(lit.as_str())
                } else if self.ch.is_digit(10) {
                    let mut lit = String::from(self.read_char());
                    while self.input.get(self.position)?.is_digit(10) {
                        lit.push(self.read_char());
                    }
                    Token::INT(lit.parse::<i32>().unwrap()) // FIXME better error handling
                } else {
                    Token::ILLEGAL(self.ch.to_string())
                }
            }
        };
        self.read_char();
        Some(tok)
    }

    fn read_char(&mut self) -> char {
        self.ch = *self.input.get(self.position).unwrap_or(&'\0'); // FIXME
        self.position += 1;
        self.ch
    }

    fn peek_char(&self) -> Option<char> {
        // if self.peek_position >= self.input.len() {
        //     '\0'
        // } else {
        //     self.input[l.readPosition]
        // }
        if let Some(ch) = self.input.get(self.peek_position) {
            Some(*ch)
        } else {
            None
        }
    }

    fn skip_whitespace(&mut self) {
        while self.ch == ' ' || self.ch == '\t' || self.ch == '\n' || self.ch == '\r' {
            self.read_char();
        }
    }
}

```