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:

1 Like

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!()
    }
}
5 Likes

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.

1 Like

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.

3 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.