Ответ на ваш исходный вопрос заключается в том, что 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;
});
}
(детская площадка)