Ownership in methods

Hi everyone. I'm following the book Crafting Interpreters and I am implementing the vm interpreter in rust. I have a compiler struct that contains a parser struct and a mutable reference to a vector of bytes (which is called a chunk like the author of the book named it). Here is a snippet of the relevant pieces of code:

struct Compiler<'a> {
    parser: Parser,
    chunk: &'a mut Chunk,
}

struct Parser {
    current: Option<Token>,
    previous: Option<Token>,
}

impl<'a> Compiler<'a> {
    fn emit_opcode(&mut self, op: OpCode) {
        self.chunk.write(op, self.parser.previous.unwrap().line);
    }
}

If I define this emit_opcode this way I get the error cannot move out of self.parser.previous which is behind a mutable reference. In my understanding this error is due to the fact that the unwrap method consumes the receiver and I cannot consume a field that I don't have the ownership of. One of the suggestions from the compiler is consider calling .as_ref() or .as_mut() to borrow the type's contents. The documentation that shows up when I hover over the as_ref method states Converts from &Option<T> to Option<&T>.After I follow the compiler's suggestion and insert this method the error actually goes away, but I'm confused as to why this is so. If the as_ref method converts a value into a reference does that prevent unwrap from taking ownership of the field? The unwrap signature does not change after I insert the as_ref method, so does it mean it consumes the reference? And if so, why am I still able to repeat the same line afterwards and not have an error stating that the parser field moved out?

It might help if you desugar the method calls. Then

self.parser.previous.unwrap()

desugars to

Option::unwrap(self.parser.previous)

whereas

self.parser.previous.as_ref().unwrap()

desugars to

Option::unwrap(Option::as_ref(&self.parser.previous))

The important thing of note is that as_ref accepts a reference. So the latter code, using &self.parser.previous will only create a reference to self.parser.previous whereas the former code will try to move it.

In both cases, the call to Option::unwrap moves its argument, however in the second case the argument that gets moved is not the original Option<Token> but instead a newly generated Option<&Token> value returned from Option::as_ref and that method created this new option by only using a reference to the actual original Option<Token>.


For further illustration, the type signatures of the functions involved are

  • for the first version, Option::unwrap is used as a fn(Option<Token>) -> Token

  • for the second version, Option::as_ref is used as a fn(&Option<Token>) -> Option<&Token> and Option::unwrap is used with the signature fn(Option<&Token>) -> &Token.

    • the overall operation ….as_ref().unwrap() thus operates essentially like a function with signature fn(&Option<Token>) -> &Token
4 Likes

By newly generated value you mean the as_ref method copies the original value ?

No, the value is not copied. Instead a newly generated Option instance is created that stores a reference to that original value (if existing and not None).

1 Like

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.