1

请考虑以下示例(游乐场):

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似乎满足编译器(注意'a之前缺少的dyn):

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

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

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

1 回答 1

5

首先要了解的是,它&|| ()有一个'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
}

要理解的第二件事是,一旦实例化了一个类型,就无法更改它。这包括它的任何通用参数,包括生命周期。一旦你有了 a Dog<'static>,它将永远是 a Dog<'static>,你不能将它转换为 a ,因为它的Dog<'1>匿名生命周期'1比 短'static

这有一些重要的含义,其中之一是您的set_formatter方法可能不像您认为的那样表现。一旦你有了一个Dog<'static>,你只能'static格式化程序传递给set_formatter. 该方法如下所示:

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

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

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

因此,既然我们已经了解了所有这些上下文,那么让我们来回答您的实际问题。

println!("{}", x);当我在闭包内注释掉时,为什么问题会消失?我仍在传递一个我希望编译器抱怨的临时文件,但事实并非如此。

为什么会失败,并附有评论:

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'staticx

当我在最后注释掉时,为什么问题会消失dog.bark();?同样,我仍在传递一个已释放的临时闭包,但现在编译器很高兴。为什么?

这种奇怪的边缘情况似乎只发生在我们没有明确地对dog变量进行类型注释时Dog<'static>。当变量没有显式类型注释时,编译器会尝试推断其类型,但它会很懒惰地这样做,并尝试尽可能灵活,从而使程序员受益于怀疑,以使代码编译。即使没有,它确实应该引发编译错误,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
}

既然我们已经走了这么远,让我们回答你的非官方第三个问题,由我转述:

为什么摆脱方法中的'ainset_formatter会让编译器满意?

因为它改变了这实际上是什么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闭包传递给 aDog<'static>尽管这毫无意义,因为该方法实际上并没有做任何事情,并且当您实际尝试在Dog<'static>结构中设置闭包时,编译器会再次抱怨。

于 2021-01-31T23:57:09.053 回答