Rust:无法引用 return 值中的局部变量 - 但 "local variable" 已传递给调用者

Rust: Cannot reference local variable in return value - but the "local variable" is passed to the caller

编写一个简单的解释器让我陷入了与借用检查器的战斗。

#[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>, Position<'a>, &'a Context<'a>),
    Int(i64, Position<'a>, Position<'a>, &'a Context<'a>),
    Nothing(Position<'a>, Position<'a>, &'a Context<'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)?))
}

错误是“不能 return 引用局部变量 `context` 的值”和(次要)“借用移动值:`context`”:

error[E0515]: cannot return value referencing local variable `context`
   --> src\basic.rs:732:2
    |
732 |     Ok((context, interpreter_visit(&ast?, &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, interpreter_visit(&ast?, &context)?))
    |         ------- value moved here          ^^^^^^^^ value borrowed here after move

据我了解:上下文引用了几个依赖于生命周期的结构。在这种情况下,这些结构的值是静态的,正如我通过将生命周期参数显式设置为 'static 而编译器没有抱怨所看到的那样。 interpreter_visit 函数需要借用上下文,因为它被传递给几个独立的函数,包括递归地传递给它自己。此外,interpreter_visit returns BASICVals 引用上下文本身。因此,上下文需要比 run return 长。我试图通过将上下文本身作为 return 值的一部分传递来实现这一点,从而让调用者控制它的生命。但是现在,我在实际使用之前将上下文移动到 return 值?这是没有意义的。我应该能够在 return 值的另一部分中引用 return 值的一部分,因为这两个值都使它脱离函数“alive”。我试过:

问题可能在于结果和错误。该错误没有引用上下文,但是在 interpreter_visit 中给它一个单独的生命周期打破了我迄今为止能够实现的整个谨慎平衡。

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

这显然是 Rust 的借用检查器无法解决的问题。借用检查器无法理解 Box of context 将存在于堆中,因此比函数 return 持续时间更长,因此可以通过 interpreter_visit 的 return 值“合法”引用它本身逃脱功能。这种情况下的解决方案是通过不安全(即原始指针)来规避借用检查。像这样:

let context = Box::new(Context {
    display_name: "<program>",
    parent: None,
    parent_entry_pos: Position::default(),
});
// Obtain a pointer to a location on the heap
let context_ptr: *const Context = &*context;
// outsmart the borrow checker
let result = interpreter_visit(&ast?, unsafe { &*context_ptr })?;
// The original box is passed back, so it is destroyed safely.
// Because the result lives as long as the context as required by the lifetime,
// we cannot get a pointer to uninitialized memory through the value and its context.
Ok((context, result))

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

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