如何解决“借用时临时价值下降”,捕获闭包
请考虑以下示例 (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
的实现中去掉显式的生命周期注解似乎让编译器满意(注意 'a
之前缺少的 dyn
):
pub fn set_formatter(&mut self,_fmt: & dyn Fn() -> ()) -> () {}
但是,我不明白以下内容:
- 为什么当我在闭包中注释掉
println!("{}",x);
时问题会消失?我仍在传递一个我希望编译器抱怨的临时文件,但它没有。 - 为什么当我在最后注释掉
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>
,我们可以用 '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
生命周期,因为它不再借用非 'static
变量 { {1}}。
为什么当我在最后注释掉 x
时问题就消失了?同样,我仍在传递一个临时关闭,该关闭已被释放,但现在编译器很高兴。为什么?
这种奇怪的边缘情况似乎只发生在我们没有用 dog.bark();
显式地对 dog
变量进行类型注释时。当一个变量没有明确的类型注解时,编译器试图推断它的类型,但它是懒惰的,并试图尽可能灵活,给程序员带来怀疑的好处,以便使代码编译。即使没有 Dog<'static>
它真的应该抛出一个编译错误,但它不会出于任何神秘的原因。关键是不是 dog.bark()
行导致代码无法编译,无论如何它都应该无法在 dog.bark()
行编译,但是无论出于何种原因,编译器都懒得抛出错误,直到您在该违规行之后再次尝试使用 set_formatter
。即使只是删除 dog
也会触发错误:
dog
既然我们已经走了这么远,让我们回答你的非官方第三个问题,由我解释:
为什么去掉 struct Dog<'a> {
format: &'a dyn Fn(),_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
}
方法中的 'a
会让编译器满意?
因为它改变了 set_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
虽然它毫无意义,因为该方法实际上并没有做任何事情,并且编译器会在您实际尝试设置Dog<'static>
结构中的闭包。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。