Skip to content

RFC-032: Унифицированная модификация выражений spawn

Этот документ определяет синтаксис spawn, рефакторинг AST/IR и расширение системы типов. Семантика поведения во время выполнения (гранулярность декомпозиции задач, ownership, области видимости, распространение ошибок, типы ресурсов, вложенность) описана в RFC-024: Семантика параллельной среды выполнения на основе spawn.

Оба RFC совместно определяют spawn — 024 отвечает на вопрос «что делать», 032 отвечает на вопрос «как представить».

Ключевая идея: spawn не должен модифицировать только блоки {}. Он может модифицировать любое выражение. spawn for — это не специальный синтаксис, а естественная комбинация spawn + выражение for.

Аннотация

Расширение spawn от spawn { } (только блоки) до spawn <expr> (любое выражение). Expr::SpawnFor удаляется из AST, естественной заменой становится Expr::Spawn { body: Expr::For { .. } }. Типы структур выражений (Block, For, While, If и др.) входят в систему типов как новые варианты MonoType, а Spawn<T> оборачивает вычислительные структуры для параллельного выполнения — это метка времени компиляции, которая стирается после проверки.

Мотивация

Зачем нужны эти изменения?

Текущий spawn for x in items { body } представляет собой независимую комбинацию ключевых слов, а в AST есть специальный Expr::SpawnFor для его представления. Это нарушает ортогональность языка:

  1. Неединый синтаксис: spawn может модифицировать только блоки {}, spawn for — жёстко закодированное исключение
  2. Отсутствие ортогональности: комбинации вроде spawn while, spawn if невозможно выразить естественно
  3. Неполная система типов: spawn невидим в системе типов, получить параллельную структуру через рефлексию типов невозможно

Текущие проблемы

rust
// В AST два варианта spawn
Spawn { body: Box<Block>, span: Span },         // spawn { ... }
SpawnFor { var, var_mut, iterable, body, span },  // spawn for x in items { ... }

// В MonoType только типы значений, нет типов вычислительных структур
// spawn { a, b } тип = Tuple(T_a, T_b)  ← теряется информация "это spawn"
// spawn for    тип = List(T)             ← теряется информация "это data parallelism"

Предложение

Основной дизайн

spawn <expr>: spawn модифицирует любое выражение. Форма выражения определяет, как DAG декомпозирует задачи.

Всё является типом: MonoType расширяется с «типов значений» до «типов значений + типов вычислительных структур». Каждая ключевая структура выражения имеет соответствующий тип в системе типов. Spawn<T> оборачивает вычислительные структуры для параллельного выполнения — это метка времени компиляции, которая стирается после проверки.

Ментальная модель пользователя

spawn = «взять это выражение и выполнить параллельно». Форма выражения определяет способ декомпозиции:

Форма выраженияПоведение параллелизмаТип
spawn { a, b, c }a, b, c независимо параллельноSpawn(Block(Tuple(T_a, T_b, T_c)))
spawn for x in items { f(x) }N итераций независимо параллельноSpawn(ForExpr { body_ty: List(T) })
spawn while cond { step() }Каждая итерация — независимая задачаSpawn(WhileExpr { body_ty: List(T) })
spawn if c { a } else { b }Выбранная ветвь целиком — spawn доменSpawn(IfExpr { then_ty: T_a, else_ty: Some(T_b) })
spawn call(x)Вызов сам по себе — одна задачаSpawn(Call { fn_ty: Fn(A→R), result_ty: R })
spawn 42Одиночная задачаSpawn(Int)

Компилятор отвечает за DAG-анализ для определения зависимостей, среда выполнения планирует по модели GMP — независимые задачи отправляются в рабочую очередь, воркеры соревнуются за их выполнение. Полная синхронизация блокирует до завершения всех задач.

Отличие от Go: в Go go — это «выбросить и забыть», в YaoXiang spawn — это «разложить для параллельного выполнения, дождаться завершения всех, затем продолжить».

Ортогональность управления потоком

КомбинацияСемантикаРазличия
spawn for x in items { body }Data parallelism: каждая итерация = задачаDAG межитерационного анализа зависимостей
for x in items spawn { body }Каждая итерация создаёт spawn доменБез межитерационного анализа
spawn while cond { body }Conditional parallelism: каждая итерация = задачаМежитерационные зависимости обеспечиваются условием
while cond spawn { body }Каждая итерация создаёт spawn доменСемантика отличается, но не требует специальной обработки
spawn if c { a } else { b }Весь if-else — один spawn доменВыполнение выбирает ветвь по условию
if c spawn { a } else { b }Только одна ветвь spawnif выражение инкапсулирует spawn внутри

Устранённая сложность

  • Expr::SpawnFor удаляется из AST
  • SpawnForAnalysis удаляется из DAG-анализа
  • spawn for больше не обрабатывается как комбинация ключевых слов в парсере
  • Ir::SpawnFor удаляется из IR

Детальный дизайн

1. Уровень AST

До:

rust
Spawn { body: Box<Block>, span: Span },         // spawn { ... }
SpawnFor { var, var_mut, iterable, body, span },  // spawn for x in items { ... }

После:

rust
Spawn { body: Box<Expr>, span: Span },           // spawn <любое выражение>

Expr::SpawnFor удаляется. AST представление spawn for x in items { body }:

rust
Expr::Spawn {
    body: Box::new(Expr::For {
        var: "x",
        iterable: items,
        body: body_block,
        ..
    })
}

Особый случай IF:

ЗаписьAST структура
spawn if cond { a } else { b }Spawn { body: Expr::If { ... } }
if cond spawn { a } else { b }Expr::If { then: Spawn { body: {a} }, else: {b} }

Обе семантики отличаются, но обе являются естественными комбинациями, не требующими специальных правил.

2. Уровень Parser

spawn имеет самый низкий приоритет связывания (как return), поглощает всё последующее выражение:

spawn a + b        →  spawn (a + b)         ≠  (spawn a) + b
spawn f(x).y       →  spawn (f(x).y)

Изменение Parser: в pratt/nud.rs spawn больше не требует {, а вызывает общий парсинг выражений:

token spawn → parse_expr(min_precedence) → Expr::Spawn { body: expr }

spawn for больше не обрабатывается как комбинация ключевых слов — for обрабатывается общим синтаксическим анализатором как Expr::For, spawn только оборачивает.

3. Система типов

Новые варианты MonoType:

rust
// ========== Типы вычислительных структур ==========

/// Выражение блока {}
Block(Box<MonoType>),

/// Выражение for-цикла
ForExpr { body_ty: Box<MonoType> },

/// Выражение while-цикла
WhileExpr { body_ty: Box<MonoType> },

/// Выражение if-else ветвления
IfExpr {
    then_ty: Box<MonoType>,
    else_ty: Option<Box<MonoType>>,
},

/// Выражение вызова функции
Call {
    fn_ty: Box<MonoType>,
    result_ty: Box<MonoType>,
},

/// Spawn обёртка параллелизма: внутреннее выражение выполняется параллельно
/// Метка времени компиляции, стирается после проверки типов
Spawn(Box<MonoType>),

Правила вывода типов: каждое выражение возвращает «тип вычислительной структуры». Без обёртки Spawn = последовательное выполнение, с обёрткой Spawn = параллельное выполнение. После проверки типов Spawn стирается, тип понижается до внутреннего типа значения.

Процесс проверки типов:

  1. Вывести тип T тела выражения (тип вычислительной структуры)
  2. Если обёрнуто в spawn, обернуть в Spawn(T)
  3. При выводе присваивания деконструировать: results: List(Data) = spawn for ... {} — из Spawn(ForExpr { body_ty: List(Data) }) извлечь List(Data)

Spawn<T> стирается после проверки типов, среде выполнения не нужно знать, откуда данные — из параллельного или последовательного выполнения. Но compile-time рефлексия (type_of(x)) может получить полную параллельную топологию.

4. Уровень DAG-анализа

Два входа объединяются в один:

rust
/// Унифицированный вход: диспетчеризация по виду тела выражения
fn analyze_spawn_expr(body: &Expr, ...) -> SpawnAnalysis {
    match body {
        Expr::Block(block)       => analyze_block_tasks(block, ...),
        Expr::For { .. }         => analyze_iter_tasks(IterKind::For, body, ...),
        Expr::While { .. }       => analyze_iter_tasks(IterKind::While, body, ...),
        Expr::If { .. }          => analyze_if_task(body, ...),
        _                        => single_task(body, ...),
    }
}

Унифицированная структура результата:

rust
struct SpawnAnalysis {
    source: TaskSource,
    plan: ExecutionPlan,
}

enum TaskSource {
    /// spawn { a, b, c } — N прямых подвыражений, известных на этапе компиляции
    Explicit(Vec<TaskInfo>),
    /// spawn for/while — N задач, генерируемых во время выполнения итерацией
    Iterate {
        kind: IterKind,
        iter_var: String,
        iterable: Option<Expr>,      // for имеет, while не имеет
        condition: Option<Expr>,     // while имеет, for не имеет
        body: Block,
        reads: HashSet<String>,
        writes: HashSet<String>,
        resource_vars: HashSet<String>,
    },
}

enum IterKind { For, While }

Структура SpawnForAnalysis удаляется.

Вид bodyКак декомпозируется в задачи
Expr::BlockПрямые подвыражения → список задач
Expr::ForКаждая итерация → задача (data parallelism)
Expr::WhileКаждая итерация → задача
Expr::IfВыбранная ветвь целиком → задача
Expr::Call / другоеСамо выражение → задача

После завершения DAG-анализа среда выполнения планирует по модели GMP — независимые задачи отправляются в рабочую очередь, воркеры соревнуются за выполнение.

5. Уровень IR / Codegen

Ir::SpawnFor удаляется. Унифицируется в Ir::Spawn с информацией TaskSource.

Перевод HIR → IR генерирует вызовы среды выполнения согласно SpawnAnalysis.source:

  • TaskSource::Explicit(tasks) → список задач, известный на этапе компиляции
  • TaskSource::Iterate { .. } → раскрытие во время выполнения (компилятор управляет,类似 par_iter但 нулевая стоимость)

6. Уровень Placement

Текущие две ветви объединяются в одну:

rust
// До
Expr::Spawn { body, .. } => self.check_block(body),
Expr::SpawnFor { body, iterable, .. } => {
    self.check_expr(iterable);
    self.check_block(body);
}

// После
Expr::Spawn { body, .. } => self.check_expr(body),   // body — это Expr, рекурсия

7. Обратная совместимость

Существующий код с spawn for сохраняет семантику, парсер автоматически парсит spawn for x in items { body } как Expr::Spawn { body: Expr::For }. Внутреннее представление меняется, видимое поведение для пользователя остаётся неизменным.

Новый синтаксис естественно доступен:

yx
spawn while has_next() {
    item = next()
    process(item)
}

spawn if use_cache {
    load_from_cache(key)
} else {
    fetch(key)
}

Компромиссы

Преимущества

  1. Синтаксическая ортогональность: spawn + любая управляющая конструкция = естественная параллельная комбинация
  2. Всё является типом: система типов полностью записывает вычислительную структуру, compile-time рефлексия получает параллельную топологию
  3. Устранение особых случаев: удаление Expr::SpawnFor и связанного кода特殊ной обработки
  4. Расширяемость: будущие новые управляющие конструкции автоматически комбинируются с spawn,无需修改 spawn логики

Недостатки

  1. Раздувание системы типов: 6 новых вариантов MonoType, увеличение сложности проверки типов
  2. Нарушающее изменение: внутренние AST/IR представления меняются, нужно обновить весь код, потребляющий Expr::SpawnFor
  3. Вывод типов выражений: каждое выражение теперь должно возвращать тип вычислительной структуры, большой охват изменений

Альтернативные решения

РешениеПочему не выбрано
Сохранить spawn for как независимый синтаксисНарушает ортогональность, становится единственным исключением в языке
spawn только для {}, data parallelism через стандартную библиотеку par_iterСпособность языка опускается до библиотечного уровня, теряется compile-time DAG-анализ и обнаружение конфликтов ресурсов
Удалить только SpawnFor, но не вводить типы вычислительных структур в систему типовСистема типов теряет способность к рефлексии, spawn невидим на уровне типов

Связь с RFC-019

Шесть вариантов MonoType, вводимые этим RFC (Block/ForExpr/WhileExpr/IfExpr/Call/Spawn), являются встроенным подмножеством компилятора из RFC-019: Типоориентированная гомоиконность. Ключевая идея RFC-019 «синтаксические структуры входят в систему типов» реализуется здесь так: 6 вычислительных структур, понимаемых компилятором изначально, имеют соответствующие типовые представления. Пользователи не могут через SyntaxRule определять новые типы вычислительных структур, но этих 6 встроенных уже достаточно для покрытия всех ключевых управляющих конструкций.

Интеграция с管道 доказательств

Шесть вариантов MonoType существуют для того, чтобы сообщить RFC-027 Compile-time провайдер верификациикакова форма проверяемого утверждения. Сам провайдер отвечает за реальную работу доказательства (анализ свободных переменных, классификация эффектов, анализ алиасинга, обнаружение конфликтов), а MonoType делает только одно — предоставляет структурированный интерфейс ввода.

Отображение вариант → утверждение

ТипФорма утвержденияСтратегия доказательства
Spawn(ForExpr { body_ty })Data parallelism: N итерационных задач без межитерационных конфликтовИзвлечь свободные переменные body → классифицировать эффекты → проверить отсутствие Write(Shared) / &mut(Shared)
Spawn(WhileExpr { body_ty })Conditional parallelism: каждая итерация независима + нет межитерационных каузальных зависимостейТо же + проверить отсутствие побочных эффектов в условии итерации
Spawn(Block(T))Явная группа задач: межзадачные зависимости задаются DAGПроверить DAG-зависимости — каждый вход задачи доступен на момент её запуска
Spawn(IfExpr { then_ty, else_ty })Branch spawn: выбранная ветвь целиком — один spawn доменВыбор ветви без конфликтов, рекурсивная проверка body
Spawn(Call { fn_ty, result_ty })Call spawn: вызываемая функция как независимая задачаПроверить чистоту или изолированность функции
Spawn(T) (значение, напр. spawn 42)Однозначный spawn: без параллелизмаТривиально проходит

Сценарии доказательства

Сценарий 1 — Чистый data parallelism (пройдено):

yaoxiang
items = [1, 2, 3, 4, 5]
results = spawn for item in items { item * 2 }
// Тип: Spawn(ForExpr { body_ty: List(Int) })
  1. Свободные переменные: item (локальная циклу, независимая копия каждой итерации), items (внешняя, только чтение в body)
  2. Классификация эффектов:全部 Read(Local) или Read(Shared), без записей
  3. Доказано ✓

Сценарий 2 — Только чтение shared (пройдено):

yaoxiang
config = load_config()
results = spawn for item in items { process(item, config) }
// Тип: Spawn(ForExpr { body_ty: List(Result) })
  1. Свободные переменные: item (Read(Local)), config (внешняя, без пути записи в body → Read(Shared))
  2. Классификация эффектов:全部 только чтение
  3. Доказано ✓

Сценарий 3 — Конфликт записи (отклонено):

yaoxiang
mut counter = 0
spawn for item in items { counter += 1 }
  1. Свободные переменные: item (Read(Local)), counter (внешняя, += десахаризуется в запись)
  2. Классификация эффектов: counter — Write(Shared), межитерационная запись в одну память
  3. Экземпляр конфликта: Write(task_0, counter) ∧ Write(task_1, counter) = True
  4. Опровергнуто ✗ → Компиляционная ошибка: Ошибка: межитерационный конфликт записи в теле spawn for. Переменная counter записывается несколькими параллельными задачами.

Сценарий 4 — while + stateful итератор (предупреждение/отклонение):

yaoxiang
spawn while iter.has_next() {
    item = iter.next()
    process(item)
}
// Тип: Spawn(WhileExpr { body_ty: List(Processed) })
  1. Свободные переменные: iter (внешняя, next()&mut self&mut(Shared))
  2. next() модифицирует состояние итератора, итерация N+1 зависит от побочного эффекта итерации N
  3. Это не независимая задача → нарушение ограничения независимости Spawn(WhileExpr)
  4. Компилятор сообщает о межитерационной каузальной зависимости, рекомендует использовать spawn for

Сценарий 5 — spawn if (пройдено):

yaoxiang
result = spawn if use_cache { load(key) } else { fetch(key) }
// Тип: Spawn(IfExpr { then_ty: T, else_ty: Option(T) })
  1. Выполняется только одна ветвь, межзадачных конфликтов нет
  2. Если в body есть вложенный spawn — рекурсивная проверка
  3. Доказано ✓

Сценарий 6 — spawn block межзадачные зависимости (DAG + верификация провайдера):

yaoxiang
spawn {
    a = fetch_user(id)
    b = fetch_orders(a.user_id)  // зависит от a
    c = compute_stats()           // независим
}
// Тип: Spawn(Block(Tuple(User, Orders, Stats)))
  1. DAG-анализ: a и c независимы (могут быть параллельны), b зависит от a (планируется после a)
  2. Верификация провайдера: вход b (a.user_id) вычислен до запуска b
  3. Доказано ✓

Что НЕ делает MonoType

ДелаетНе делает
Идентифицирует форму утвержденияНе выполняет доказательство
Записывает вычислительную структуру на уровне типовНе заменяет DAG-анализ
Предоставляет типовой ввод для провайдера RFC-027Не заменяет анализ свободных переменных, алиасинг, обнаружение конфликтов

Реальную работу доказательства выполняют стандартные аналитические проходы компилятора. Ценность MonoType в том, что эти проходы调度ируются в единой типовой структуре — провайдеру верификации не нужно писать специальные ветки для каждого вида AST узла.

Стратегия реализации

Фазы

  1. AST + Parser: Spawn { body: Box<Expr> }, удаление SpawnFor
  2. Система типов: новые 6 вариантов MonoType, все выражения возвращают тип вычислительной структуры
  3. Унификация DAG-анализа: объединение входов, унификация перечисления TaskSource
  4. Адаптация IR / Codegen: удаление Ir::SpawnFor, унификация пути обработки
  5. Упрощение Placement: удаление ветви SpawnFor
  6. Тестирование: все существующие тесты spawn for проходят

Область влияния

Файл/директорияИзменения
frontend/core/parser/ast.rsSpawn body改为 Box<Expr>, удаление SpawnFor
frontend/core/parser/pratt/nud.rsОбработчик spawn упрощается до通用ного парсинга выражений
frontend/core/types/mono.rsНовые варианты Block/ForExpr/WhileExpr/IfExpr/Call/Spawn
frontend/core/spawn/analysis.rsУнификация входов, TaskSource объединяет Explicit + Iterate
frontend/core/spawn/placement.rsУдаление ветви SpawnFor
frontend/core/typecheck/Все узлы выражений адаптируются к выводу типов вычислительных структур
middle/core/ir.rsУдаление Ir::SpawnFor
middle/ (IR gen, codegen)Унификация пути spawn, стирание типа Spawn
tests/yaoxiang/04-concurrency/spawn_for.yxСемантика неизменна, проверка проходит

Зависимости

  • RFC-024 (spawn block параллельная модель) — ортогональное расширение данного RFC
  • RFC-010 (унифицированный синтаксис типов) — основа для изменений системы типов
  • RFC-027 (compile-time провайдер верификации) — варианты MonoType предоставляют форму утверждения для провайдера
  • RFC-019 (типоориентированная гомоиконность) — варианты MonoType являются его встроенным подмножеством компилятора

Журнал решений по дизайну

РешениеРешениеПричинаДата
Область модификации spawnЛюбое выражениеУстранение исключительности spawn for2026-06-16
Поддержка spawn whileПоддерживаетсяСинтаксическая ортогональность, низкая стоимость реализации2026-06-16
Семантика spawn ifМодифицирует весь if-elseОтличие от if spawn { }2026-06-16
Система типовВведение типов вычислительных структур«Всё является типом», поддержка compile-time рефлексии2026-06-16
Стирание типа spawnСтирается после проверки типовСреде выполнения не нужна информация о параллелизме2026-06-16
Приоритет связывания spawnСамый низкий (как return)Поглощает всё последующее выражение2026-06-16
DAG для внутренности forНе раскрывает внутренние подвыражения forПравило для прямых подвыражений неизменно, for целиком — один источник задач2026-06-16
Интеграция провайдера верификацииMonoType варианты отображаются в утверждения провайдера RFC-027Провайдеру нужно знать форму проверяемого утверждения, MonoType предоставляет структурированный ввод2026-07-03
Связь с RFC-019Встроенное подмножество компилятораПользователи не могут определять, но разделяют идею «синтаксис как тип»2026-07-03
Границы доказательства6 сценариев покрыты: чистый parallelism/только чтение shared/конфликт записи/while зависимости/spawn if/spawn blockЧётко определены обязательства по доказательству и условия отказа для каждого варианта MonoType2026-07-03

Ссылки


Жизненный цикл и судьба

СтатусМестоположениеОписание
На рассмотренииdocs/design/rfc/review/Открыто для обсуждения сообщества