Borrow checker issues when using enums and structs

I am trying to build a C compiler in Rust for a university course.
It's my first time using Rust and I am just a beginner. I know that building a compiler is a difficult task but hopefully it won't be that hard :slight_smile:

The parser struct has a token that represents the token that is currently read.
When reading tokens the parser should create the AST of the program.

The problem I have is here

cannot move out of `self.token` as enum variant `Identifier` which is behind a shared reference
  --> src/main.rs:12:15
   |
12 |         match self.token {
   |               ^^^^^^^^^^
13 |             SimpleToken::Identifier(s) => {
   |                                     -
   |                                     |
   |                                     data moved here
   |                                     move occurs because `s` has type `String`, which does not implement the `Copy` trait
   |
help: consider borrowing here
   |
12 |         match &self.token {

The full code:

struct SimpleParser {
    token: SimpleToken,
}

enum Expression {
    VarDefinition(String, u32)
}

impl SimpleParser {
    fn match_token(&self) -> Expression {
        // let token = SimpleToken::Identifier("hey".to_string());
        // problem is here.
        match self.token {
            SimpleToken::Identifier(s) => {
                Expression::VarDefinition(s, 2)
            }
            _ => panic!("Bad")
        }
    }
}

enum SimpleToken {
    Identifier(String)
}

fn main() {
}

What I want

I do not want to borrow the value of the token, I want to simply take ownership and move the String from the Identifier enum to the VarDefinition enum.

Interestingly enough, if I have a local variable and I match on it then the code compiles.

fn match_token(&self) -> Expression {
    let token = SimpleToken::Identifier("ana".to_string());
    match token {
        SimpleToken::Identifier(s) => {
            Expression::VarDefinition(s, 2)
        }
        _ => panic!("Bad")
    }
}

I don't really understand how the borrow checker works and what the issue is here.
Why does it work with a local variable but not with a variable of the struct.

I guess the reason could be that after I move the String from the Identifier to VarDefinition it is possible to access self.token and read moved data.

That means you need to either

  1. leave another String in Identifier, like an empty string (probably not what you want)
    or
  2. declare that the SimpleParser instance is unusable by taking ownership in match_token: fn match_token(self) (instead of &self).

The easiest would be to clone the String.

The parser function will be recursive in the real implementation so I am not sure if that works with match_token(self).

The easiest would be clone that is true but that introduces extra memory allocations.

After reading the identifier I will not use it again, I don't really care what happens to it afterwards because I will get the AST from the parser and not worry about tokens.

Another option that I am currently thinking of is making is borrowing the String

enum Expression<'a> {
    VarDefinition(&'a String, u32)
}

But this will pollute the whole code base with a lot of 'a s :))

Maybe you can have Identifier use a option. Use &mut self and put none in Identifier after using s. I don't know how it would influence performance since I am also new to coding , but it probably won't matter.

Like this

struct SimpleParser {
    token: SimpleToken,
}

enum Expression {
    VarDefinition(Option<String>, u32)
}

impl SimpleParser {
    fn match_token(&mut self) -> Expression {
        // let token = SimpleToken::Identifier("hey".to_string());
        // problem is here.
        match & mut self.token {
            SimpleToken::Identifier(s) => {
                Expression::VarDefinition(s.take(), 2)
            }
            _ => panic!("Bad")
        }
    }
}

enum SimpleToken {
    Identifier(Option<String>)
}

fn main() {
}

Because you...

error[E0507]: cannot move out of [...] a shared reference

You don't have a variable of the struct type. You have a variable of a shared reference of the struct type. Without interior mutability (aka shared mutability), you cannot mutate data behind a shared reference.[1] If you change the signature to match_token(self) -> Expression, it works like the local variable does (because now self is the struct type).

Other options include

  • Just clone the String, as others mentioned
  • Have a &mut self receiver and std::mem::take the String
    • Or Option<String>, as others mentioned
  • Use Arc<str> or Rc<str> for cheaper cloning without new allocation
  • Use some sort of more-proper string interning
  • Leak the source code and use &'static str
  • Run the bulk of the program further down the call stack from where the source code is stored and use &'src str, as you mentioned

  1. Even if it's not shared, you can't leave a hole in the struct that might be observed. ↩ī¸Ž

Just throwing it out there: if the thought is to call match_token on a nested Expression contained inside the parent expression, then the move of self can still be a valid use case.