我有下面的代码:
use std::thread;
fn main() {
let x: &'static mut [i32; 3] = Box::leak(Box::new([1, 2, 3]));
thread::spawn(|| dbg!(&x));
}
当我编译它时,我得到下面的错误:
error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
--> src\main.rs:10:19
|
10 | thread::spawn(|| dbg!(&x));
| ^^ - `x` is borrowed here
| |
| may outlive borrowed value `x`
|
note: function requires argument type to outlive `'static`
--> src\main.rs:10:5
|
10 | thread::spawn(|| dbg!(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `x` (and any other referenced variables), use the `move` keyword
|
10 | thread::spawn(move || dbg!(&x));
| ++++
这里它告诉我“闭包可能比当前函数更持久”。我认为“当前函数”是主函数。但是,有没有可能关闭超过主要功能?当main函数结束时,整个过程也结束了。这意味着从此进程派生的所有线程也将终止,无论它们是否已完成执行。那么,闭包是如何比主函数更长久呢?
2条答案
按热度按时间yizd12fk1#
线程在主函数结束后被杀死。
这听起来像是吹毛求疵,但这种区别很重要,因为main函数的结尾包括局部变量的销毁。这意味着首先变量被销毁,包括调用它们的
drop()
实现,然后 * 线程被杀死。这证明了这一事实:
注意,如果线程有一个
&
(非mut
)引用到_x
,在drop()
函数内部,一个&
和一个&mut
到_x
将同时存在。此外,变量包含的任何内容都肯定会在drop()
函数中被破坏,因此外部引用它绝对是未定义的行为。出于这样的原因,
main()
函数被简单地视为普通函数,并且所有普通的借用规则都适用。以上是关于为什么
main()
被视为普通函数的一般性说明,现在让我们来谈谈您的代码。看起来你试图通过
Box::leak
处理变量来克服这个问题。这绝对是一种延长main()
结尾后变量生存期的方法,但您犯了一个错误。数组本身是&'static
,但变量x
不是。当你在线程中执行&x
时,你并没有引用数组,而是引用了变量x
,而x
存在于main()
中。你需要将引用本身移动到线程中。因为这与将
Box
本身移动到线程中有什么区别并不明显,所以我将创建第二个线程以进行演示:我还将引用
&
,而不是&mut
,用于两个线程。还要注意的是,&
引用是Copy
,使得let x2 = x;
成为可能。这不会克隆原始数组,它们都指向相同的数据。当与
Mutex
配对时,这一点变得显而易见:g52tjvyc2#
关于discussion in chat
注:当我谈论“闭包”时,我指的是任何实现
FnOnce() -> T
的类型。为了简洁起见,我将使用这个特定的类型类,因为它们在下面的上下文中使用得最频繁。std::thread::spawn
的API要求派生的线程 * 可能 * 比派生它的线程存活时间长(注意,这是一个设计选择,有不同的API不要求这样,例如std::thread::scope)。为了使这一点为真,传递给新线程的闭包不能捕获任何生命周期与第一个线程绑定的引用。换句话说,它只能保存'static
引用。生 rust 的特殊之处在于,它在类型级别上执行这一点。
std::thread::spawn
具有以下签名:它在类型级别强制传递给
spawn
的闭包和它返回的值都是'static
。重要的是,rust不会将
main
函数与其他函数区别对待。如果编译器 * 可以 * 推断新线程不会比主线程存活得更久,则此类型级别的API仍然要求所有闭包都是'static
。另外,错误消息“闭包可能比当前函数更持久”的措辞可能有点误导。闭包的寿命不会超过函数。它们只是值,在它们的范围内有效,就像所有其他值一样。在这个上下文中,重要的是哪个线程可以比哪个线程更长寿。由于
std::thread::spawn
允许派生线程比调用它的线程更长寿,因此必须对调用者进行空间限制以确保安全。