Rust:无法在返回值中引用局部变量 - 但“局部变量”被传递给调用者

问题描述

编写一个简单的解释器使我与借用检查器进行了这场战斗。

#[derive(Clone,Debug)]
struct Context<'a> {
    display_name: &'a str,parent: Option<Box<Context<'a>>>,parent_entry_pos: Position<'a>,}

// --snip--

#[derive(copy,Clone,Debug)]
pub enum BASICVal<'a> {
    Float(f64,Position<'a>,&'a Context<'a>),Int(i64,nothing(Position<'a>,}

// --snip--

pub fn run<'a>(text: &'a String,filename: &'a String) -> Result<(Context<'a>,BASICVal<'a>),BASICError<'a>> {
    // generate tokens
    let mut lexer = Lexer::new(text,filename);
    let tokens = lexer.make_tokens()?;

    // parse program to AST
    let mut parser = Parser::new(tokens);
    let ast = parser.parse();

    // run the program
    let context: Context<'static> = Context {
        display_name: "<program>",parent: None,parent_entry_pos: Position::default(),};
    Ok((context,interpreter_visit(&ast?,&context)?))
}

错误是“无法返回引用局部变量`context`的值”和(次要的)“移动值的借用:`context`”:

error[E0515]: cannot return value referencing local variable `context`
   --> src\basic.rs:732:2
    |
732 |     Ok((context,&context)?))
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^--------^^^^
    |     |                                     |
    |     |                                     `context` is borrowed here
    |     returns a value referencing data owned by the current function

error[E0382]: borrow of moved value: `context`
   --> src\basic.rs:732:40
    |
727 |     let context: Context<'static> = Context {
    |         ------- move occurs because `context` has type `basic::Context<'_>`,which does not implement the `copy` trait
...
732 |     Ok((context,&context)?))
    |         ------- value moved here          ^^^^^^^^ value borrowed here after move

据我所知:上下文引用了几个依赖于生命周期的结构。在这种情况下,这些结构的值是静态的,正如我可以通过将生命周期参数显式设置为 'static 并且编译器没有抱怨而看到的。 interpreter_visit 函数需要借用上下文,因为它会以递归方式传递给多个独立函数包括自身。此外,interpreter_visit 返回引用上下文本身的 BASICVal。出于这个原因,上下文需要比 run 返回更有效。我尝试通过将上下文本身作为返回值的一部分传递来实现这一点,从而让调用者控制其生命周期。但是现在,我在实际使用它之前将上下文移动到返回值?这没有任何意义。我应该能够在返回值的另一部分引用返回值的一部分,因为这两个值都使其脱离了“活动”函数。我试过了:

  • 对上下文进行装箱,从而将其从堆栈移到堆上,但这似乎只会使事情复杂化。
  • 切换元组的顺序,但这无济于事。
  • 将 interpreter_visit 的结果存储在中间变量中,这与预期的一样无济于事。
  • 克隆 interpreter_visit 结果或上下文本身

问题可能在于结果和错误。该错误并未引用上下文,但在 interpreter_visit 中为其提供了单独的生命周期,这打破了我迄今为止能够实现的整个谨慎平衡。

解决方法

回答这个问题是为了让人们不必阅读评论线程。

Rust 的借用检查器显然无法解决这个问题。借用检查器无法理解上下文的 Box 将存在于堆中,因此持续时间比函数返回的时间长,因此可以通过本身转义函数的 interpreter_visit 的返回值“合法地”引用。这种情况下的解决方案是通过 unsafe 来绕过借用检查,即原始指针。像这样:

     public void addNode(int data) {
        Node newNode = new Node(data);

        if(head == null) {
            head = newNode;
            tail = newNode;
        }
        else
        {
            tail.next = newNode;
            tail = newNode;

        }

我在 context_ptr 中存储了一个指向上下文的原始指针。传递给 interpreter_visit 的借用值然后通过(完全内存安全的)原始指针取消引用和借用进行管道传输。这将(出于某种原因,只有 Rust 大神知道)禁用借用检查,因此提供给 interpreter_visit 的上下文数据被认为具有合法的生命周期。然而,由于我仍然在上下文数据周围传回非常安全的 Box,我可以通过让上下文没有所有者来避免造成内存泄漏。现在可能可以通过销毁上下文来传递 interpreter_visit 返回值,但由于这两个值都被立即打印并丢弃,因此我认为将来不会出现任何问题。

如果您对 Rust 的借用检查器有更深入的了解,并且认为这是一个可修复的边缘案例,没有更多我无法想出的“安全”解决方案,请发表评论,我会将其报告给 Rust团队。然而,我并不确定,尤其是因为我对 Rust 的经验和知识有限。