dar*_*nge 7 concurrency multithreading move-semantics rust
例如(取自Rust 文档):
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
Run Code Online (Sandbox Code Playgroud)
这不是关于做什么 的问题move,而是关于为什么需要指定.
如果您希望闭包拥有外部 value 的所有权,是否有理由不使用move关键字?如果move是经常在这些情况下需要,有没有为什么存在任何理由move不能只是暗示/省略?例如:
let v = vec![1, 2, 3];
let handle = thread::spawn(/* move is implied here */ || {
// Compiler recognizes that `v` exists outside of this closure's
// scope and does black magic to make sure the closure takes
// ownership of `v`.
println!("Here's a vector: {:?}", v);
});
Run Code Online (Sandbox Code Playgroud)
上面的例子给出了以下编译错误:
closure may outlive the current function, but it borrows `v`, which is owned by the current function
Run Code Online (Sandbox Code Playgroud)
当错误通过添加 神奇地消失时move,我不禁自问:为什么我不想要这种行为?
我并不是在暗示所需的语法有什么问题。我只是想move从比我更了解 Rust 的人那里获得更深入的了解。:)
这完全是关于生命周期注释,以及 Rust 很久以前做出的设计决策。
看,您的thread::spawn示例无法编译的原因是因为它需要一个'static闭包。由于新线程可以比产生它的代码运行更长的时间,我们必须确保在调用者返回后任何捕获的数据都保持活动状态。正如您所指出的,解决方案是将数据的所有权传递给move.
但是'static约束是一个生命周期注解,而 Rust 的一个基本原则是生命周期注解永远不会影响运行时行为。换句话说,生命周期注解只是为了让编译器相信代码是正确的;他们不能改变代码的作用。
如果 Rust 是move根据被调用者是否期望来推断关键字的'static,那么thread::spawn当捕获的数据被丢弃时,改变生命周期可能会改变。这意味着生命周期注解正在影响运行时行为,这违背了这一基本原则。我们不能打破这个规则,所以move关键字保持不变。
让我们可以自由地改变生命周期推断的工作方式,这允许像非词法生命周期(NLL)这样的改进。
因此,像mrustc这样的替代 Rust 实现可以通过忽略生命周期来节省工作量。
大部分编译器都假设生命周期以这种方式工作,因此要实现这一点,将需要付出巨大的努力并获得可疑的收益。(请参阅Aaron Turon 的这篇文章;它是关于专业化,而不是闭包,但它的要点也同样适用。)
这里实际上有一些事情在起作用。为了帮助回答您的问题,我们必须首先了解为什么move存在。
Rust 有 3 种类型的闭包:
当你创建一个闭包时,Rust 会根据闭包如何使用环境中的值来推断要使用哪个特征。闭包捕获其环境的方式取决于其类型。AFnOnce按值捕获(如果类型可以,可以是移动或复制Copy)、FnMut可变借用和Fn不可变借用。但是,如果您move在声明闭包时使用关键字,它将始终“按值捕获”,或者在捕获它之前获得环境的所有权。因此,move关键字与FnOnces无关,但它改变了Fns 和FnMuts 捕获数据的方式。
就您的示例而言,Rust 将闭包的类型推断为 a Fn,因为println!只需要引用它正在打印的值(您链接的 Rust 书页在解释没有 的错误时谈到了这一点move)。因此,闭包尝试借用v,并且适用标准的生命周期规则。由于thread::spawn要求传递给它的闭包具有'static生命周期,因此捕获的环境也必须具有'static生命周期,该生命周期v不会超过生命周期,从而导致错误。因此,您必须明确指定您希望闭包拥有v.
这可以通过将闭包更改为编译器会推断为FnOnce-- 的东西来进一步举例说明|| v,作为一个简单的例子。由于编译器推断闭包是 a FnOnce,因此v默认情况下它按值捕获,并且该行let handle = thread::spawn(|| v);编译时不需要move.