"temporary value dropped while borrowed" 带有捕获闭包

"temporary value dropped while borrowed" with capturing closure

请考虑以下示例 (playground):

struct Animal<'a> {
    format: &'a dyn Fn() -> (),
}

impl <'a>Animal<'a> {
    pub fn set_formatter(&mut self, _fmt: &'a dyn Fn() -> ()) -> () {} // Getting rid of 'a here satisfies the compiler
    pub fn bark(&self) {}
}

fn main() {
    let mut dog: Animal = Animal { format: &|| {()} };
    let x = 0;
    dog.set_formatter(&|| {
        println!("{}", x); // Commenting this out gets rid of the error. Why?
    });
    dog.bark(); // Commenting this out gets rid of the error. Why?
}

这会产生以下编译错误:

Compiling playground v0.0.1 (/playground)
error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:13:24
   |
13 |       dog.set_formatter(&|| {
   |  ________________________^
14 | |         println!("{}", x); // Commenting this out gets rid of the error. Why?
15 | |     });
   | |     ^ - temporary value is freed at the end of this statement
   | |_____|
   |       creates a temporary which is freed while still in use
16 |       dog.bark(); // Commenting this out gets rid of the error. Why?
   |       --- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error: aborting due to previous error

For more information about this error, try `rustc --explain E0716`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

这种情况是有道理的,因为我传递给 dog.set_formatter(...) 的闭包确实是一个临时的(我猜)在执行到 dog.bark();.

时被释放

我知道在 set_formatter 的实现中摆脱显式生命周期注解似乎可以满足编译器的要求(注意 dyn 之前缺少的 'a):

pub fn set_formatter(&mut self, _fmt: & dyn Fn() -> ()) -> () {}

但是,我不明白以下内容:

  1. 为什么当我在闭包中注释掉 println!("{}", x); 时问题消失了?我仍在传递一个临时文件,我希望编译器会抱怨,但它没有。
  2. 为什么我在最后注释掉 dog.bark(); 后问题就消失了?同样,我仍在传递一个已释放的临时闭包,但现在编译器很高兴。为什么?

首先要了解的是&|| ()有一个'static生命周期:

fn main() {
    let closure: &'static dyn Fn() = &|| (); // compiles
}

另一件值得一提的事情是闭包的生命周期不能超过它从环境中捕获的任何变量的生命周期,这就是为什么如果我们试图将一个非静态变量传递给我们的静态闭包它会失败编译:

fn main() {
    let x = 0; // non-static temporary variable
    let closure: &'static dyn Fn() = &|| {
        println!("{}", x); // x reference captured by closure
    }; // error: trying to capture non-static variable in static closure
}

我们会回到那个话题。无论如何,如果我有一个在引用上通用的结构,并且我将一个 'static 引用传递给它,我将有一个该结构的 'static 实例:

struct Dog<'a> {
    format: &'a dyn Fn(),
}

fn main() {
    let dog: Dog<'static> = Dog { format: &|| () }; // compiles
}

第二点要理解的是,一旦你实例化了一个类型,你就不能改变它。这包括它的任何通用参数,包括生命周期。一旦你有了 Dog<'static>,它将 总是 成为 Dog<'static>,你无法将其转换为 Dog<'1> 一段时间的匿名生命周期 '1'static.

这有一些强烈的影响,其中之一是您的 set_formatter 方法可能并不像您认为的那样运行。一旦你有了 Dog<'static>,你可以 'static 格式化程序传递给 set_formatter。该方法如下所示:

impl<'a> Dog<'a> {
    fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}

但是因为我们知道我们正在使用 Dog<'static> 我们可以用 'static 替换通用生命周期参数 'a 以查看我们真正使用的是什么:

// what the impl would be for Dog<'static>
impl Dog {
    fn set_formatter(&mut self, _fmt: &'static dyn Fn()) {}
}

现在我们已经了解了所有这些背景,让我们开始讨论您的实际问题。

Why does the problem go away when I comment out println!("{}", x); inside the closure? I'm still passing a temporary which I expect the compiler to complain about, but it doesn't.

为什么失败,附注:

struct Dog<'a> {
    format: &'a dyn Fn(),
}

impl<'a> Dog<'a> {
    fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}

fn main() {
    let mut dog: Dog<'static> = Dog { format: &|| () };
    
    // x is a temporary value on the stack with a non-'static lifetime
    let x = 0;
    
    // this closure captures x by reference which ALSO gives it a non-'static lifetime
    // and you CANNOT pass a non-'static closure to a Dog<'static>
    dog.set_formatter(&|| {
        println!("{}", x); 
    });
}

通过注释掉行 println!("{}", x); 来“修复”此错误的原因是它再次为闭包提供了 'static 生命周期,因为它不再借用非 'static变量 x.

Why does the problem go away when I comment out dog.bark(); at the end? Again, I'm still passing a temporary closure which is freed but now the compiler is happy. Why?

这种奇怪的边缘情况似乎只在我们没有使用 Dog<'static> 明确地对 dog 变量进行类型注释时才会发生。当一个变量没有明确的类型注解时,编译器会尝试推断它的类型,但它这样做是懒惰的,并试图尽可能灵活,给程序员带来怀疑的好处,以便使代码编译。它 确实应该 抛出一个编译错误,即使没有 dog.bark() 但它不会出于任何神秘的原因。关键是它不是使代码无法编译的 dog.bark() 行,它应该无论如何都无法在 set_formatter 行编译,但无论出于何种原因,编译器都懒得抛出一个错误,直到您尝试在该违规行之后再次使用 dog 。即使只是删除 dog 也会触发错误:

struct Dog<'a> {
    format: &'a dyn Fn(),
}

impl<'a> Dog<'a> {
    fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}

fn main() {
    let mut dog = Dog { format: &|| () };
    let x = 0;
    
    dog.set_formatter(&|| {
        println!("{}", x); 
    });
    
    drop(dog); // triggers "temp freed" error above
}

既然我们已经走到这一步了,让我们来回答您非正式的第三个问题,由我转述:

Why does getting rid of the 'a in the set_formatter method satisfy the compiler?

因为它改变了 Dog<'static> 的有效内容:

// what the impl would be for Dog<'static>
impl Dog {
    fn set_formatter(&mut self, _fmt: &'static dyn Fn()) {}
}

进入这个:

// what the impl would now be for Dog<'static>
impl Dog {
    fn set_formatter(&mut self, _fmt: &dyn Fn()) {}
}

所以现在您可以将非 'static 闭包传递给 Dog<'static> 尽管这毫无意义,因为该方法实际上没有做任何事情,编译器会在您实际尝试时再次抱怨在 Dog<'static> 结构中设置闭包。