【问题标题】:error[E0507]: Cannot move out of borrowed content错误[E0507]:无法移出借用内容
【发布时间】:2017-10-15 07:07:04
【问题描述】:

我正在尝试在 Rust 中制作一个词法分析器,虽然对它相对较新但具有 C/C++ 背景。我在以下代码中遇到 Rust 如何分配内存的问题,这会生成错误“无法移出借用的内容”。我已经阅读了cargo --explain E0507,其中详细介绍了可能的解决方案,但我正在努力掌握 Rust 和 C/C++ 如何管理内存之间的潜在差异。本质上,我想了解如何在 Rust 中管理动态内存(或者更好的方式来实现我正在做的事情)。

错误是:

error[E0507]: cannot move out of borrowed content
  --> <anon>:65:16
   |
65 |         return self.read_tok.unwrap();
   |                ^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of borrowed content
  --> <anon>:73:16
   |
73 |         return self.peek_tok.unwrap();
   |                ^^^^ cannot move out of borrowed content

error: aborting due to 2 previous errors

代码是:

use std::fmt;

#[derive(Debug, PartialEq)]
pub enum TokenType {
    EndOfFile,
    Illegal
}

pub struct Token {
    token_type: TokenType,
    value: String
}

impl Token {
    pub fn new(token_type: TokenType, value: String) -> Token {
        return Token {
            token_type: token_type,
            value: value
        };
    }

    pub fn is_token_type(&self, token_type: TokenType) -> bool {
        return self.token_type == token_type;
    }
}

impl fmt::Debug for Token {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}[{}]", self.token_type, self.value)
    }
}

pub struct Lexer {
    input: String,
    read_pos: usize,
    peek_pos: usize,
    ch: char,
    read_tok: Option<Token>,
    peek_tok: Option<Token>
}

const EOF: char = 0 as char;

impl Lexer {
    pub fn new(input: &str) -> Lexer {
        return Lexer {
            input: input.to_string(),
            read_pos: 0,
            peek_pos: 1,
            ch: EOF,
            read_tok: None,
            peek_tok: None
        };
    }

    pub fn next_token(&mut self) -> Token {
        if self.peek_tok.is_none() {
            self.read_tok = Some(self.get_next_token());
        } else {
            self.read_tok = self.peek_tok.take();
        }

        return self.read_tok.unwrap();
    }

    pub fn peek_token(&mut self) -> Token {
        if self.peek_tok.is_none() {
            self.peek_tok = Some(self.get_next_token());
        }

        return self.peek_tok.unwrap();
    }

    fn get_next_token(&mut self) -> Token {
        let ch = self.next_char();
        let tok: Token;

        match ch {
            EOF => { tok = Token::new(TokenType::EndOfFile, "".to_string()); }
            _   => { tok = Token::new(TokenType::Illegal, ch.to_string()); }
        }

        return tok;
    }

    fn next_char(&mut self) -> char {
        if self.peek_pos >= self.input.len() {
            self.ch = EOF;
        } else {
            self.ch = self.input.chars().nth(self.peek_pos).unwrap();
        }

        self.read_pos = self.peek_pos;
        self.peek_pos += 1;

        return self.ch;
    }
}


fn main() {
    let input = "let x = 5;";
    let mut l = Lexer::new(input);

    loop {
        let t = l.next_token();
        println!("{:?}", t);

        if t.is_token_type(TokenType::EndOfFile) {
            break;
        }
    }
}

Rust 游乐场链接:https://play.rust-lang.org/?gist=bc85fafa35a5cbbd5ac4066aef9e333c&version=stable&backtrace=0https://play.rust-lang.org/?gist=21cba64f53488ee0a9389c0191c47134&version=stable&backtrace=0

我已经设法用 C++ 翻译了一个工作实现,这可能会提供更多关于我想要实现的目标的信息:

#include <string>
#include <iostream>

enum TokenType {
    ENDOFFILE,
    ILLEGAL
};

class Token {
private:
    enum TokenType token_type;
    std::string value;

public:
    Token(enum TokenType token_type, std::string value)
    {
        this->token_type = token_type;
        this->value = value;
    }

    bool is_token_type(enum TokenType token_type)
    {
        return this->token_type == token_type;
    }

    std::string to_string()
    {
        std::string tok;

        switch (this->token_type) {
        case ENDOFFILE:
            tok = "EndOfFile";
            break;
        case ILLEGAL:
            tok = "Illegal[" + this->value + "]";
            break;
        }

        return tok;
    }
};

class Lexer {
private:
    std::string input;
    int read_pos;
    int peek_pos;
    char ch;
    Token *read_tok;
    Token *peek_tok;

    Token *get_next_token() {
        char c = this->next_char();
        std::string c_str;
        Token *t;

        c_str.push_back(c);

        switch (c) {
        case 0:
            t = new Token(ENDOFFILE, "");
            break;
        default:
            t = new Token(ILLEGAL, c_str);
        }

        return t;
    }

    char next_char()
    {
        if (this->peek_pos >= this->input.length()) {
            this->ch = 0;
        } else {
            this->ch = input.at(this->peek_pos);
        }

        this->read_pos = this->peek_pos;
        this->peek_pos += 1;

        return this->ch;
    }

public:
    Lexer (std::string input)
    {
        this->input = input;
        this->read_pos = -1;
        this->peek_pos = 0;
        this->ch = 0;
        this->read_tok = NULL;
        this->peek_tok = NULL;
    }

    Token *next_token()
    {
        if (this->read_tok != NULL) {
            delete this->read_tok;
        }

        if (this->peek_tok == NULL) {
            this->read_tok = this->get_next_token();
        } else {
            this->read_tok = this->peek_tok;
            this->peek_tok = NULL;
        }

        return this->read_tok;
    }

    Token *peek_token()
    {
        if (this->peek_tok == NULL) {
            this->peek_tok = this->get_next_token();
        }

        return this->peek_tok;
    }
};

int main(int argc, char **argv)
{
    std::string input = "let x = 5;";
    Lexer l = Lexer(input);

    while (1) {
        Token *t = l.next_token();
        std::cout << t->to_string() << std::endl;

        if (t->is_token_type(ENDOFFILE)) {
            break;
        }
    }

    return 0;
}

【问题讨论】:

  • 恐怕您的问题存在大量问题:(1) 请尝试将代码缩小到minimal reproducible example。 (2) 您应该包含编译器提供的完整、准确的错误消息,包括触发错误的行。 (3) 如何在 Rust 中管理内存的通用答案有望在 The Book 中得到解答。我建议你阅读OwnershipReferences and Borrowing 的章节。
  • 另请注意,除了您链接的Stack Overflow questions with that title,还有许多其他Stack Overflow questions with that title,这些可能有助于您理解问题。
  • @E_net4 (1) 在保留基本功能的同时,我可以合理地做到这一点。 (2) 从 rust playground 输出中复制了错误。 (3) 虽然我已经阅读了这些章节并掌握了这些想法,但这些示例似乎对相对初学者有用(因此我在这里)。
  • MCVE 不必保留所有功能,但足以重现您面临的特定问题。我非常怀疑我们是否必须观察所有代码(包括 C++ 中的示例)才能理解问题。

标签: rust lexer


【解决方案1】:

您非常接近正确,但您的代码存在两个问题。

首先,正如编译器告诉你的,以下是禁止的:

self.read_tok = self.peek_tok;
self.peek_tok = None;

第一行尝试将Option&lt;Token&gt; 对象移出self.peek_tok。在 Rust 中,对象可以移出变量,但不能移出结构字段或切片下标。这是因为编译器可以在移动后检查该变量是否未被使用,并安排其析构函数不被调用。这对于存储在结构字段或切片内部的对象是不可能的,至少在不增加每个结构或容器的开销的情况下是不可能的。

只要将对象存储在支持移动的中间容器中,就可以将对象移出结构。幸运的是,Option 就是这样一个容器,它的 take() 方法正是为此目的而设计的:

self.read_tok = self.peek_tok.take()

Option::take() 从选项中移动对象,将其替换为None,然后返回该对象。

其次,一旦上述问题得到解决,编译器就会在next_tokenpeek_tokenreturn 语句中抱怨“移出借用内容”,因为它们试图将对象移出Option。在这里,您可以选择克隆Token,或者使用上面的Option::take() 将其移出选项。克隆方法需要将#[derive(Clone)] 添加到TokenTypeToken,并将返回值更改为:

// Use as_ref() to convert Option<Token> to Option<&Token>,
// which is unwrapped and the Token inside cloned
self.read_tok.as_ref().unwrap().clone()

通过这些更改,示例编译,尽管它仍然将输入标记为非法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-04
    • 1970-01-01
    • 2015-03-18
    • 2015-03-25
    • 1970-01-01
    • 2015-04-20
    • 1970-01-01
    相关资源
    最近更新 更多