Idiomatic approach to OOP state mutations`

I know that this is the classic Rust beginner's problem. I feel like I have an understanding of why this is a problem, but I don't have enough background to understand what the idiomatic solution(s) to it are.

I'm trying to implement a basic parser - where I read tokens one at a time, advance the position, and build a syntax tree (not shown here).

pub struct Parser {
    // this field is actually going to be static for the lifecycle of the parser
    tokens: Vec<Token>,
    // this field is mutated as the parser advances
    position: usize,
}

impl Parser {
    // fetch the current token
    fn peek(&self) ->  Option<&Token> {
        if self.position < self.tokens.len() {
            Some(&self.tokens[self.position])
        } else {
            None
        }
    }

    // fetch the current token and advance the position
    fn read(&mut self) -> Option<&Token> {
        let token = self.peek();
        if token != None {
            self.position += 1;
        }

        token
    }
}

My understanding of why this is a problem (please correct me if I'm misunderstanding): The token pointer returned by peek() is associated with the lifecycle of self, so without further information, the borrow checker has to assume I have exclusive ownership of the whole reference. The self.position increment also requires ownership of self, hence the conflict.

I'd like to be able to indicate to the compiler (which I feel does make what I'm doing safe) is that I'm actually only mutating position, and that token is a pointer to an immutable structure (as far as the application logic is concerned).

I think that understanding the problem is simpler than understanding the idiomatic way to fix it - hence why I'm coming here :slight_smile:

To implement what your comments are saying, you can store the tokens separately from the parser:

pub struct Parser<'a> {
    tokens: &'a [Token],
    position: usize,
}

impl<'a> Parser<'a> {
    fn peek(&self) ->  Option<&'a Token> {
        todo!()
    }

    fn read(&mut self) -> Option<&'a Token> {
        todo!()
    }
}

Thanks, that's exactly what I needed, and helps with my understanding.

My understanding of why this solution works: Because the struct now contains a pointer to an array, we need to explicitly indicate that the contents of the array have a different lifecycle (because the memory location itself clearly isn't owned by the parser). The peek() & read() methods are returning pointers to memory addresses in this array, so it's intuitive that they should be able to return a pointer with the same lifecycle, and the compiler now is able to prove that I'm not holding onto some reference which points to self.

Yes. Also note that by having Parser borrow [Token] we're declaring that the list of tokens is not going to be changed as long as the Parser exists.