Rust 错误[E0507]:无法移出借用的内容

Rust 错误[E0507]:无法移出借用的内容,rust,lexer,Rust,Lexer,我试图在Rust中制作一个lexer,虽然我对它比较陌生,但有C/C++的背景。我在下面的代码中遇到了Rust如何分配内存的问题,这会生成错误“无法移出借用的内容”。我已经阅读了cargo--explain E0507,其中详细介绍了可能的解决方案,但我很难理解Rust和C/C++如何管理内存之间的根本区别。本质上,我想了解如何在Rust中管理动态内存(或者更好地实现我正在做的事情) 错误是: error[E0507]: cannot move out of borrowed content

我试图在Rust中制作一个lexer,虽然我对它比较陌生,但有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
错误[E0507]:无法移出借用的内容
-->

我已经成功地翻译了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;
}
#包括
#包括
枚举标记类型{
Endofile,
非法的
};
类令牌{
私人:
枚举令牌类型令牌类型;
std::字符串值;
公众:
令牌(枚举令牌类型令牌类型,标准::字符串值)
{
此->令牌类型=令牌类型;
这个->值=值;
}
bool是令牌类型(枚举令牌类型令牌类型)
{
返回此->令牌类型==令牌类型;
}
std::字符串到_字符串()
{
std::字符串tok;
开关(此->令牌类型){
案例结尾:
tok=“EndOfFile”;
打破
案件不合法:
tok=“非法[”+此->值+”;
打破
}
返回tok;
}
};
类Lexer{
私人:
std::字符串输入;
int read_pos;
内部peek_pos;
char ch;
令牌*readtok;
代币*peek_-tok;
令牌*获取下一个令牌(){
char c=this->next_char();
std::字符串c_str;
令牌*t;
c_str.推回(c);
开关(c){
案例0:
t=新令牌(ENDOFFILE,“”);
打破
违约:
t=新令牌(非法,c_str);
}
返回t;
}
char next_char()
{
如果(this->peek\u pos>=this->input.length()){
这->ch=0;
}否则{
这个->通道=输入。在(这个->窥视位置);
}
此->读取位置=此->查看位置;
此->peek_pos+=1;
返回此->ch;
}
公众:
Lexer(标准::字符串输入)
{
这个->输入=输入;
此->读取位置=-1;
该->窥视位置=0;
这->ch=0;
此->读取\u tok=NULL;
此->peek_tok=NULL;
}
令牌*下一个令牌()
{
如果(本->读_-tok!=NULL){
删除此->读取\u-tok;
}
如果(此->peek_tok==NULL){
此->读取\u tok=此->获取下一个\u令牌();
}否则{
本->阅读本=本->偷看本;
此->peek_tok=NULL;
}
返回此->读取\u-tok;
}
令牌*peek_令牌()
{
如果(此->peek_tok==NULL){
此->偷看=此->获取下一个令牌();
}
返回此->peek_tok;
}
};
int main(int argc,字符**argv)
{
std::string input=“let x=5;”;
Lexer l=Lexer(输入);
而(1){
令牌*t=l.下一个令牌();
std::cout to_string()是_token_类型(ENDOFFILE)){
打破
}
}
返回0;
}

您很快就把它做好了,但是您的代码有两个问题

首先,正如编译器告诉您的,禁止以下操作:

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

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

self.read_tok = self.peek_tok.take()
Option::take()
从选项中移动对象,将其替换为
None
,然后返回对象

其次,一旦上述问题得到解决,编译器会在
next_-token
peek_-token
return
语句中抱怨“移出借用的内容”,因为它们试图将对象移出
选项。在这里,您可以选择克隆
标记
,或者如上所述使用
选项::take()
将其移出选项。克隆方法需要将
#[派生(克隆)]
添加到
TokenType
Token
,并将返回更改为:

// 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()
//使用as_ref()将选项转换为选项,
//打开包装并克隆其中的令牌
self.read_tok.as_ref().unwrap().clone()

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

您已经非常接近正确的输入,但是您的代码存在两个问题

首先,正如编译器告诉您的,禁止以下操作:

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

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