How to call method with &mut self from inside anot

2019-04-10 07:36发布

问题:

I've got a Scanner struct that has a scan(&mut self) method implemented. It looks like this.

pub struct Scanner {
    input: String,
    output: Vec<String>,
    state: ScannerState,
}

impl Scanner {
    pub fn scan(&mut self) {
        self.state = ScannerState::CharMode;
        for character in self.input.chars() {
            match character {
                i @ '0'...'9'   => self.output.push(format!("Integer digit: {}", i)),
                '+'             => self.output.push("Addition operator: +".to_string()),
                '-'             => self.output.push("Subtraction operator: -".to_string()),
                '*'             => self.output.push("Multiplication operator: *".to_string()),
                '/'             => self.output.push("Division operator: /".to_string()),
                '%'             => self.output.push("Modulus operator: %".to_string()),
                '^'             => self.output.push("Exponent operator: ^".to_string()),
                '='             => self.output.push("Assignment operator: =".to_string()),
                ';'             => self.output.push("Statement terminator: ;".to_string()),
                c @ 'a'...'z'| c @ 'A'...'Z'
                                => self.output.push(format!("Variable name: {}", c)),
                ' '             => self.output.push("Space, ignoring.".to_string()),
                z @ _           => self.output.push(format!("Unrecognized token: {}", z))
            }
        }
    }        
}

However, as you might suspect, the state of the Scanner will change, and it will need to treat tokens differently in different states. Therefore, it would be useful to call a private method on the Scanner from within the scan() method itself, akin to this:

pub fn scan(&mut self) {
    self.state = ScannerState::CharMode;

    while self.state == ScannerState::CharMode {
        for character in self.input.chars() {
            self.char_match(character);
        }
        self.state = ScannerState::Done;
    }
}

fn char_match(&mut self, c: char) {
    match c {
        '+'             => self.output.push("Addition operator: +".to_string()),
        '-'             => self.output.push("Subtraction operator: -".to_string()),
        '*'             => self.output.push("Multiplication operator: *".to_string()),
        '/'             => self.output.push("Division operator: /".to_string()),
        '%'             => self.output.push("Modulus operator: %".to_string()),
        '^'             => self.output.push("Exponent operator: ^".to_string()),
        '='             => self.output.push("Assignment operator: =".to_string()),
        ';'             => self.output.push("Statement terminator: ;".to_string()),
        ' '             => self.output.push("Space, ignoring.".to_string()),
        'q'             => self.state = ScannerState::QuitMode,
        i @ '0'...'9'   => self.output.push(format!("Integer digit: {}", i)),
        c @ 'a'...'z'   => self.output.push(format!("Variable name: {}", c)),
        z @ _           => self.output.push(format!("Unrecognized token: {}", z))
    }
}

But wait! We can't do that, Rust tells us:

src/scanner.rs:34:17: 34:21 error: cannot borrow `*self` as mutable because `self.input` is also borrowed as immutable
src/scanner.rs:34                 self.char_match(character);
                                  ^~~~

Yet our char_match() method NEEDS to have a mutable reference to self, since it pushes, and push()ing on a Vec requires mutability. My question then, is given the preceding knowledge, what is the ideal way to remedy this situation?

Will I simply have to write scan() as a long method?

回答1:

My suggestion would be to make char_match stateless:

pub fn scan(&mut self) {
    self.state = ScannerState::CharMode;

    while self.state == ScannerState::CharMode {
        for character in self.input.chars() {
            match char_match(character) {
                Some(string) => self.output.push(string),
                None => self.state = ScannerState::QuitMode
            }
        }
        self.state = ScannerState::Done;
    }
}

fn char_match(c: char) -> Option<String> {
    Some(match c {
        '+' => "Addition operator: +".into(),
        '-' => "Subtraction operator: -".into(),
        '*' => "Multiplication operator: *".into(),
        '/' => "Division operator: /".into(),
        '%' => "Modulus operator: %".into(),
        '^' => "Exponent operator: ^".into(),
        '=' => "Assignment operator: =".into(),
        ';' => "Statement terminator: ;".into(),
        ' ' => "Space, ignoring.".into(),

        'q' => return None,

        i @ '0'...'9' => format!("Integer digit: {}", i),
        c @ 'a'...'z' => format!("Variable name: {}", c),
        z @ _         => format!("Unrecognized token: {}", z)
    })
}

This avoids borrowing altogether, and is also more composable.



回答2:

char_match does not need mutable access to self. Mutable access to self.output and self.state will be enough. If you don't give it access to self.input the compiler will no longer complain.



标签: rust