为什么在产生线程时 Rust 强制在 i32 的情况下使用移动?

man*_*nth 2 rust

我是 Rust 的新手,看起来我在这里严重遗漏了一些概念。

use std::thread;

fn main() {
    let mut children = vec![];

    //spawn threads
    for i in 0..10 {
        let c = thread::spawn(|| {
            println!("thread id is {}", i);
        });
        children.push(c);
    }

    for j in children {
        j.join().expect("thread joining issue");
    }
}
Run Code Online (Sandbox Code Playgroud)

它失败并出现错误:

error[E0373]: closure may outlive the current function, but it borrows `i`, which is owned by the current function
Run Code Online (Sandbox Code Playgroud)

由于iis的类型i32,并且不涉及引用,因此 Rust 不应该复制该值而不是强制为move

SCa*_*lla 7

你原来的问题的答案是println!借用它的论点。但是,正如您在评论中指出的那样,即使(显然)将整数移动到闭包中仍然会导致编译错误。

出于此答案的目的,我们将使用此代码。

fn use_closure<F: FnOnce() + 'static>(_: F) {}

fn main() {
    let x: i32 = 0;
    use_closure(|| {
        let _y = x;
    });
}
Run Code Online (Sandbox Code Playgroud)

(操场)

use_closure模拟thread::spawn原始代码中的操作:它使用一个类型必须为 的闭包'static

尝试编译这个会产生错误

fn use_closure<F: FnOnce() + 'static>(_: F) {}

fn main() {
    let x: i32 = 0;
    use_closure(|| {
        let _y = x;
    });
}
Run Code Online (Sandbox Code Playgroud)

等等,什么?

error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
 --> src/main.rs:5:17
  |
5 |     use_closure(|| {
  |                 ^^ may outlive borrowed value `x`
6 |         let _y = x;
  |                  - `x` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:5:5
  |
5 | /     use_closure(|| {
6 | |         let _y = x;
7 | |     });
  | |______^
help: to force the closure to take ownership of `x` (and any other referenced variables), use the `move` keyword
  |
5 |     use_closure(move || {
  |                 ^^^^^^^
Run Code Online (Sandbox Code Playgroud)

为什么在x那里借?不应该是副本吗?答案在于闭包的“捕获模式”。从文档

编译器更喜欢通过不可变借用,然后是唯一的不可变借用(见下文),可变借用,最后是移动来捕获封闭变量。它将选择允许闭包编译的第一个选项。仅根据闭包表达式的内容进行选择;编译器不会考虑周围的代码,例如相关变量的生命周期。

正是因为x有一个Copy类型,闭包本身可以通过一个不可变的借用来编译。给定一个不可变的借用x(称为bor),我们可以对_ywith进行赋值_y = *bor。这不是“移出引用后面的数据”,因为这是复制而不是移动。

然而,由于闭包借用了一个局部变量,它的类型不会是'static,所以它不能在use_closureor 中使用thread::spawn

尝试使用 not 类型的相同代码Copy,它实际上工作得很好,因为闭包必须x通过移动它来捕获。

fn use_closure<F: FnOnce() + 'static>(_: F) {}

fn main() {
    let x: Vec<i32> = vec![];
    use_closure(|| {
        let _y = x;
    });
}
Run Code Online (Sandbox Code Playgroud)

(操场)


当然,正如您已经知道的,解决方案是move在闭包前使用关键字。这会强制所有捕获的变量移动到闭包中。由于不会借用变量,因此闭包将具有静态类型,并且可以在use_closure或 中使用thread::spawn

fn use_closure<F: FnOnce() + 'static>(_: F) {}

fn main() {
    let x: i32 = 0;
    use_closure(move || {
        let _y = x;
    });
}
Run Code Online (Sandbox Code Playgroud)

(操场)