为什么在闭包内克隆数据不能防止错误 "closure may outlive the current function"?
Why does cloning data inside a closure not prevent the error "closure may outlive the current function"?
我用 gtk-rs 构建了一个 GTK 应用程序。当我构建主 window 时,我想使用一些动态参数,例如 window 高度。我创建了一个包含所有此类设置的结构,并希望将其用作构建 UI:
函数的输入参数
fn main() {
let application =
gtk::Application::new(Some("id"), Default::default())
.expect("Initialization failed...");
let config = Config {width: 100., height: 100.};
application.connect_activate(|app| {
build_ui(app, config.clone());
});
// Use config further
application.run(&args().collect::<Vec<_>>());
}
#[derive(Debug, Clone)]
pub struct Config {
pub width: f64,
pub height: f64,
}
fn build_ui(application: >k::Application, config: Config) {
...
}
我在调用 build_ui
时不能使用对 config
的引用,因为这个函数可以在主函数完成后调用,因此配置结构不再存在。
我的想法是创建配置结构的副本(它只是一些原始变量),它与原始变量分开存在,因此我不会 运行 陷入生命周期或所有权问题。
这是正确的方法吗?我究竟做错了什么?我得到了与借用配置结构相同的错误:
error[E0373]: closure may outlive the current function, but it borrows `config`, which is owned by the current function
--> src/main.rs:36:34
|
36 | application.connect_activate(|app| {
| ^^^^^ may outlive borrowed value `config`
37 | build_ui(app, config.clone());
| ------ `config` is borrowed here
一般说明
类似问题的最小再现:
fn move_and_print(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("Hello");
let print_cloned_s = || println!("{}", s.clone());
move_and_print(s);
print_cloned_s();
}
编译器抱怨:
error[E0505]: cannot move out of `s` because it is borrowed
我想克隆 s
以避免借用,这样以后就可以使用它了。那么,编译器怎么会说s
是借来的呢?
前面的推理是完全正确的,但是,有一个微妙之处:Clone::clone
的签名是clone(&self) -> Self
。所以当clone
被调用时,数据被克隆函数!
借用了
解决方案是在创建闭包之前克隆数据,然后将其移动到闭包中:
fn move_and_print(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("Hello");
// I clone `s` BEFORE creating the closure:
let cloned_s = s.clone();
// Then I move the cloned data into the closure:
let print_cloned_s = move || println!("{}", cloned_s);
move_and_print(s);
print_cloned_s();
}
解决您的实际错误
如我所说,您必须克隆配置并将此克隆移动到闭包中:
let cloned_config = config.clone();
application.connect_activate(move |app| {
build_ui(app, cloned_config.clone());
});
您还必须添加第二个克隆调用以允许闭包是 Fn
而不是 FnOnce
。事实上,如果你将你的配置移动到 build_ui
中,这个函数就不能被使用两次。有关详细信息,请参阅 。
如果我很了解您的需要,config
旨在成为必须共享的 read-only 配置。在这种情况下,我根本不会移动它,例如将 build_ui
的签名更改为:
fn build_ui(application: >k::Application, config: &Config)
讨厌说认可的答案不是很准确。它是正确的,但与 OP 代码有细微差别。实际上,如果仔细阅读原始代码,没有理由相信 rustc
不能得出局部变量 config
比 connect_activate
函数调用还长的结论。它出于其他原因拒绝它。
一个更准确的最小可重现示例是:
fn reference_and_print(s: &str) {
println!("{}", s);
}
fn closure_and_print<F: Fn()>(f: F) {
f();
}
fn main() {
let s = "Hello";
reference_and_print(s);
closure_and_print(|| {
println!("{}", s);
});
reference_and_print(s);
}
编译通过。但是,如果只改变一行:
fn closure_and_print<F: Fn() + 'static>(f: F) {
f();
}
这会导致 may outlive borrowed value
错误。挺意外的。
事实上,通过检查 gtk-rs
代码,我注意到带有 'static
绑定的闭包无处不在。除非您拥有它们,否则没有什么比 'static
更长久。这就是为什么在使用 gtk-rs
:
时必须使用 move
闭包来拥有捕获的变量
let cloned = config.clone();
application.connect_activate(move |app| {
build_ui(app, cloned);
});
我用 gtk-rs 构建了一个 GTK 应用程序。当我构建主 window 时,我想使用一些动态参数,例如 window 高度。我创建了一个包含所有此类设置的结构,并希望将其用作构建 UI:
函数的输入参数fn main() {
let application =
gtk::Application::new(Some("id"), Default::default())
.expect("Initialization failed...");
let config = Config {width: 100., height: 100.};
application.connect_activate(|app| {
build_ui(app, config.clone());
});
// Use config further
application.run(&args().collect::<Vec<_>>());
}
#[derive(Debug, Clone)]
pub struct Config {
pub width: f64,
pub height: f64,
}
fn build_ui(application: >k::Application, config: Config) {
...
}
我在调用 build_ui
时不能使用对 config
的引用,因为这个函数可以在主函数完成后调用,因此配置结构不再存在。
我的想法是创建配置结构的副本(它只是一些原始变量),它与原始变量分开存在,因此我不会 运行 陷入生命周期或所有权问题。
这是正确的方法吗?我究竟做错了什么?我得到了与借用配置结构相同的错误:
error[E0373]: closure may outlive the current function, but it borrows `config`, which is owned by the current function
--> src/main.rs:36:34
|
36 | application.connect_activate(|app| {
| ^^^^^ may outlive borrowed value `config`
37 | build_ui(app, config.clone());
| ------ `config` is borrowed here
一般说明
类似问题的最小再现:
fn move_and_print(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("Hello");
let print_cloned_s = || println!("{}", s.clone());
move_and_print(s);
print_cloned_s();
}
编译器抱怨:
error[E0505]: cannot move out of `s` because it is borrowed
我想克隆 s
以避免借用,这样以后就可以使用它了。那么,编译器怎么会说s
是借来的呢?
前面的推理是完全正确的,但是,有一个微妙之处:Clone::clone
的签名是clone(&self) -> Self
。所以当clone
被调用时,数据被克隆函数!
解决方案是在创建闭包之前克隆数据,然后将其移动到闭包中:
fn move_and_print(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("Hello");
// I clone `s` BEFORE creating the closure:
let cloned_s = s.clone();
// Then I move the cloned data into the closure:
let print_cloned_s = move || println!("{}", cloned_s);
move_and_print(s);
print_cloned_s();
}
解决您的实际错误
如我所说,您必须克隆配置并将此克隆移动到闭包中:
let cloned_config = config.clone();
application.connect_activate(move |app| {
build_ui(app, cloned_config.clone());
});
您还必须添加第二个克隆调用以允许闭包是 Fn
而不是 FnOnce
。事实上,如果你将你的配置移动到 build_ui
中,这个函数就不能被使用两次。有关详细信息,请参阅
如果我很了解您的需要,config
旨在成为必须共享的 read-only 配置。在这种情况下,我根本不会移动它,例如将 build_ui
的签名更改为:
fn build_ui(application: >k::Application, config: &Config)
讨厌说认可的答案不是很准确。它是正确的,但与 OP 代码有细微差别。实际上,如果仔细阅读原始代码,没有理由相信 rustc
不能得出局部变量 config
比 connect_activate
函数调用还长的结论。它出于其他原因拒绝它。
一个更准确的最小可重现示例是:
fn reference_and_print(s: &str) {
println!("{}", s);
}
fn closure_and_print<F: Fn()>(f: F) {
f();
}
fn main() {
let s = "Hello";
reference_and_print(s);
closure_and_print(|| {
println!("{}", s);
});
reference_and_print(s);
}
编译通过。但是,如果只改变一行:
fn closure_and_print<F: Fn() + 'static>(f: F) {
f();
}
这会导致 may outlive borrowed value
错误。挺意外的。
事实上,通过检查 gtk-rs
代码,我注意到带有 'static
绑定的闭包无处不在。除非您拥有它们,否则没有什么比 'static
更长久。这就是为什么在使用 gtk-rs
:
move
闭包来拥有捕获的变量
let cloned = config.clone();
application.connect_activate(move |app| {
build_ui(app, cloned);
});