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)
但是,我不明白以下内容:
println!("{}", x);为什么当我在闭包内注释掉时问题就消失了?我仍在传递一个临时值,我希望编译器会抱怨它,但事实并非如此。dog.bark();为什么当我最后注释掉时问题就消失了?再次,我仍然传递一个已释放的临时闭包,但现在编译器很高兴。为什么?首先要了解的是它&|| ()有一个'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>。