“借用期间价值下降”并捕获关闭

Att*_*Kun 1 closures lifetime rust borrow-checker trait-objects

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

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?
}
Run Code Online (Sandbox Code Playgroud)

这会产生以下编译错误:

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.
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为我传递给的闭包dog.set_formatter(...)确实是一个临时的,(我猜)当执行进行到 时它会被释放dog.bark();

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

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.
Run Code Online (Sandbox Code Playgroud)

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

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

kfe*_*v91 6

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

fn main() {
    let closure: &'static dyn Fn() = &|| (); // compiles
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

我们稍后会回来讨论这一点。无论如何,所以如果我有一个对引用通用的结构,并且我向它传递了一个'static引用,我将拥有'static该结构的一个实例:

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

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

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

这有一些强烈的含义,其中之一是您的set_formatter方法可能不会按照您认为的方式运行。一旦你有了 a,Dog<'static>你就只能'static将格式化程序传递给set_formatter. 该方法如下所示:

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

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

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

现在我们已经了解了所有这些背景,让我们来回答您的实际问题。

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); 
    });
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

既然我们已经走到这一步了,让我们回答你的非官方第三个问题,由我解释:

为什么去掉'a方法中的就能set_formatter让编译器满意?

因为它改变了它的有效用途Dog<'static>

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

进入这个:

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

因此,现在您可以将非'static闭包传递给 a Dog<'static>,尽管这是毫无意义的,因为该方法实际上并不执行任何操作,并且当您实际尝试在结构中设置闭包时,编译器会再次抱怨Dog<'static>