5.2 KiB
Отличный и глубокий вопрос! На него часто дают неполный ответ ("перемещается замыкание"), но давайте разберем конкретно по байтам, что именно физически перемещается в новый поток в вашем коде.
Короткий ответ
Когда вы пишете move ||, в новый поток перемещается сама структура замыкания (closure struct).
В вашем случае эта структура имеет размер всего 8 байт (на 64-битной системе) и содержит внутри только один Arc.
Подробный разбор ("под капот")
1. Что такое замыкание в Rust?
Компилятор Rust превращает ваше замыкание в анонимную структуру (struct). Поля этой структуры — это все переменные из внешнего окружения, которые вы захватили.
В вашем коде внутри thread::spawn:
move || {
// Используются:
// 1. i (копия, примитив)
// 2. chunk_size (копия, примитив)
// 3. data_ref (это Arc<Vec<i32>>)
// ... логика
}
Компилятор генерирует примерно такую структуру:
// Псевдокод сгенерированной структуры
struct ClosureEnvironment {
i: usize, // 8 байт
chunk_size: usize, // 8 байт
data_ref: Arc<Vec<i32>>, // 8 байт (указатель на кучу)
}
2. Что делает move?
Ключевое слово move приказывает компилятору: "Забери владение всеми переменными, которые используются внутри {...}, и положи их в эту структуру ClosureEnvironment".
Без move компилятор попытался бы положить в структуру ссылки (&i, &data_ref), что вызвало бы ошибку компиляции, так как ссылки не могут пережить функцию sum_squares_static_arc.
3. Что физически "перемещается"?
Когда вы вызываете thread::spawn(move || ...):
- Создается экземпляр этой структуры
ClosureEnvironmentна стеке текущего потока. - Этот экземпляр перемещается (memcpy) из стека главного потока в стек нового потока (или в кучу, если
Box::new).iиchunk_sizeпросто копируются (какCopyтипы).data_ref(Arc) тоже копируется (бинарно это просто указатель0x...). Сами данные массива[i32]никуда не копируются, они лежат в куче и остаются на месте. Копируется только "умный указатель" на них.
Итоговая картина
В новый поток "улетает" маленький пакет данных (размером 24 байта: usize + usize + ptr), который содержит:
- Число
i(индекс потока). - Число
chunk_size. - Указатель
Arc(который увеличил счетчик ссылок на данные, но сами данные не трогал).
В этом и есть суть эффективности: вы передаете гигабайты данных в другой поток, физически перемещая всего 8 байт указателя Arc.
Важное уточнение про Arc
Обратите внимание, что перед циклом вы делаете:
let data_ref = Arc::clone(&data_arc);
Именно этот новый data_ref перемещается в замыкание. Оригинальный data_arc остается в main потоке нетронутым.
Каждая итерация цикла создает свой собственный "билетик" (data_ref), и именно этот билетик move забирает в новый поток.