Kal*_*son 1 closures ownership rust
我正在尝试通过智能克隆和借用来优化应用程序,并且我正在观察以下行为。下面的程序将无法运行:
fn f( string: String) {
println!("{}", string );
}
fn main() {
let my_string: String = "ABCDE".to_string();
f( my_string );
f( my_string );
}
Run Code Online (Sandbox Code Playgroud)
它会生成众所周知的“移动后使用”错误。
fn f( string: String) {
println!("{}", string );
}
fn main() {
let my_string: String = "ABCDE".to_string();
f( my_string );
f( my_string );
}
Run Code Online (Sandbox Code Playgroud)
这个可以通过克隆来解决my_string。下面的程序运行良好:
fn f( string: String) {
println!("{}", string );
}
fn main() {
let my_string: String = "ABCDE".to_string();
f( my_string.clone() );
f( my_string.clone() );
}
Run Code Online (Sandbox Code Playgroud)
但是,如果您在多线程环境中使用相同的方法,克隆就不再有帮助了。当函数调用嵌入到线程中时:
use std::thread;
fn f( string: String) {
println!("{}", string );
}
fn main() {
let my_string: String = "ABCDE".to_string();
thread::spawn( move || { f( my_string.clone() ); } );
thread::spawn( move || { f( my_string.clone() ); } );
}
Run Code Online (Sandbox Code Playgroud)
程序再次生成“移动后使用”错误:
7 | f( my_string );
| --------- value moved here
8 | f( my_string );
| ^^^^^^^^^ value used here after move
Run Code Online (Sandbox Code Playgroud)
但是,您可以通过将线程移至函数中来解决此问题,具有相同的最终效果:
use std::thread;
fn f( string: String) {
thread::spawn( move || { println!("{}", string ); } );
}
fn main() {
let my_string: String = "ABCDE".to_string();
f( my_string.clone() );
f( my_string.clone() );
}
Run Code Online (Sandbox Code Playgroud)
上面的程序运行良好。或者,如果您愿意,可以提前克隆并在第二个函数调用my_string中使用克隆:
use std::thread;
fn f( string: String) {
println!("{}", string );
}
fn main() {
let my_string: String = "ABCDE".to_string();
let my_second_string: String = my_string.clone();
thread::spawn( move || { f( my_string.clone() ); } );
thread::spawn( move || { f( my_second_string ); } );
}
Run Code Online (Sandbox Code Playgroud)
这看起来有点像反复试验,但某些理论也许可以解释它。
还有一个关于“移动后使用”错误的问题。另一个问题讨论 的效果,而这个问题则在线程环境中to_string()讨论。clone()
但是,如果您在多线程环境中使用相同的方法,克隆就不再有帮助了。[...]这看起来有点像反复试验,但某些理论也许可以解释它。
如果你做得正确的话,它确实有帮助。这里的问题是闭包意味着在闭包运行1move之前该值被移入闭包。
所以当你写的时候
thread::spawn(move || { f( my_string.clone()); });
Run Code Online (Sandbox Code Playgroud)
发生的事情是
my_string在闭包内移动my_string当您克隆时my_string已经太晚了,因为它已从外部函数移至闭包和线程内部。就好像您尝试通过更改以下内容来修复原始片段f:
thread::spawn(move || { f( my_string.clone()); });
Run Code Online (Sandbox Code Playgroud)
这显然不能解决任何问题。
通常的解决方案是所谓的“精确捕获模式”。“Capture 子句”来自 C++,它是一个原生特性,但在 Rust 中却不是。您所做的是,不是直接创建闭包,而是从块创建并返回闭包,在创建和返回闭包之前,您可以创建一堆绑定,然后将它们移动到闭包中。它本质上提供了一个隔离的“关闭设置”:
thread::spawn({
// shadow the outer `my_string` with a clone of itself
let my_string = my_string.clone();
// then move the clone into the closure
move || { f(my_string); }
});
Run Code Online (Sandbox Code Playgroud)
顺便说一句,如果您不需要修改,另一种选择String是将其放入Arc. 尽管您仍然需要在闭包外部克隆并将圆弧移动到闭包内部。优点是克隆弧只会增加其引用计数,它是原子的,因此不是免费的,但它比克隆复杂/昂贵的对象更便宜。
[1]:从技术上讲,非移动闭包也可能发生这种情况,更准确地说,move闭包将move(/ copy) 它所引用的所有内容,而非移动闭包可能会移动或只是借用,具体取决于项目的使用方式。