Почему Rust заставляет использовать move в случае i32 при порождении потоков? - PullRequest
2 голосов
/ 20 апреля 2020

Я новичок в 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");
    }
}

Сбой из-за ошибки:

error[E0373]: closure may outlive the current function, but it borrows `i`, which is owned by the current function

Поскольку тип i - это i32, и здесь нет никаких ссылок, не следует ли Rust копировать значение вместо принудительного move?

1 Ответ

7 голосов
/ 20 апреля 2020

Ответ на ваш исходный вопрос заключается в том, что println! заимствует свои аргументы. Однако, как вы указали в комментариях, даже (по-видимому) перемещение целого числа в замыкание по-прежнему вызывает ошибку компиляции.

Для целей этого ответа мы будем работать с этим кодом.

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

fn main() {
    let x: i32 = 0;
    use_closure(|| {
        let _y = x;
    });
}

(игровая площадка)

use_closure имитирует то, что thread::spawn делает в исходном коде: он использует замыкание, тип которого должен быть 'static.

Попытка скомпилировать это дает ошибку

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 || {
  |                 ^^^^^^^

Подождите, что?

6 |         let _y = x;
  |                  - `x` is borrowed here

Почему x заимствовано там? Разве это не должна быть копия? Ответ лежит в «режимах захвата» для замыканий. Начиная с документации

Компилятор предпочитает захватывать закрытую переменную с помощью неизменяемого заимствования, за которым следует уникальное неизменяемое заимствование (см. Ниже), изменяемое заимствование и, наконец, перемещение , Он выберет первый из них, который позволяет замыканию компилироваться. Выбор сделан только относительно содержания выражения замыкания; компилятор не учитывает окружающий код, например время жизни задействованных переменных.

Именно потому, что x имеет тип Copy, само замыкание может компилироваться с простым неизменным заимствованием. Учитывая неизменный заем x (назовите его bor), мы можем выполнить наше назначение на _y с _y = *bor. Это не «перемещение данных за ссылкой», потому что это копия вместо перемещения.

Однако, поскольку замыкание занимает локальную переменную, его тип не будет 'static, поэтому он не будет использоваться в use_closure или thread::spawn.

При использовании того же кода с типом, который не Copy, он на самом деле работает отлично, так как замыкание вынуждено захватывать x, перемещая его.

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

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

(игровая площадка)


Конечно, как вы уже знаете, решение заключается в использовании move Ключевое слово перед закрытием. Это заставляет все захваченные переменные быть перемещенными в замыкание. Поскольку переменная не будет заимствована, закрытие будет иметь тип stati c и сможет использоваться в use_closure или thread::spawn.

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

fn main() {
    let x: i32 = 0;
    use_closure(move || {
        let _y = x;
    });
}

(детская площадка)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...