hyperion/20-dev/00-rust/30-fearless/Untitled.md

5.2 KiB
Raw Blame History

Отличный и глубокий вопрос! На него часто дают неполный ответ ("перемещается замыкание"), но давайте разберем конкретно по байтам, что именно физически перемещается в новый поток в вашем коде.

Короткий ответ

Когда вы пишете 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 || ...):

  1. Создается экземпляр этой структуры ClosureEnvironment на стеке текущего потока.
  2. Этот экземпляр перемещается (memcpy) из стека главного потока в стек нового потока (или в кучу, если Box::new).
    • i и chunk_size просто копируются (как Copy типы).
    • data_ref (Arc) тоже копируется (бинарно это просто указатель 0x...). Сами данные массива [i32] никуда не копируются, они лежат в куче и остаются на месте. Копируется только "умный указатель" на них.

Итоговая картина

В новый поток "улетает" маленький пакет данных (размером 24 байта: usize + usize + ptr), который содержит:

  1. Число i (индекс потока).
  2. Число chunk_size.
  3. Указатель Arc (который увеличил счетчик ссылок на данные, но сами данные не трогал).

В этом и есть суть эффективности: вы передаете гигабайты данных в другой поток, физически перемещая всего 8 байт указателя Arc.

Важное уточнение про Arc

Обратите внимание, что перед циклом вы делаете:

let data_ref = Arc::clone(&data_arc);

Именно этот новый data_ref перемещается в замыкание. Оригинальный data_arc остается в main потоке нетронутым. Каждая итерация цикла создает свой собственный "билетик" (data_ref), и именно этот билетик move забирает в новый поток.

1 2 3 4 5 6 7 8 9