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 для его представления. Это нарушает ортогональность языка:
- Неединый синтаксис:
spawnможет модифицировать только блоки{},spawn for— жёстко закодированное исключение - Отсутствие ортогональности: комбинации вроде
spawn while,spawn ifневозможно выразить естественно - Неполная система типов: spawn невидим в системе типов, получить параллельную структуру через рефлексию типов невозможно
Текущие проблемы
// В 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 } | Только одна ветвь spawn | if выражение инкапсулирует spawn внутри |
Устранённая сложность
- ❌
Expr::SpawnForудаляется из AST - ❌
SpawnForAnalysisудаляется из DAG-анализа - ❌
spawn forбольше не обрабатывается как комбинация ключевых слов в парсере - ❌
Ir::SpawnForудаляется из IR
Детальный дизайн
1. Уровень AST
До:
Spawn { body: Box<Block>, span: Span }, // spawn { ... }
SpawnFor { var, var_mut, iterable, body, span }, // spawn for x in items { ... }После:
Spawn { body: Box<Expr>, span: Span }, // spawn <любое выражение>Expr::SpawnFor удаляется. AST представление spawn for x in items { body }:
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:
// ========== Типы вычислительных структур ==========
/// Выражение блока {}
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 стирается, тип понижается до внутреннего типа значения.
Процесс проверки типов:
- Вывести тип T тела выражения (тип вычислительной структуры)
- Если обёрнуто в spawn, обернуть в
Spawn(T) - При выводе присваивания деконструировать:
results: List(Data) = spawn for ... {}— изSpawn(ForExpr { body_ty: List(Data) })извлечьList(Data)
Spawn<T> стирается после проверки типов, среде выполнения не нужно знать, откуда данные — из параллельного или последовательного выполнения. Но compile-time рефлексия (type_of(x)) может получить полную параллельную топологию.
4. Уровень DAG-анализа
Два входа объединяются в один:
/// Унифицированный вход: диспетчеризация по виду тела выражения
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, ...),
}
}Унифицированная структура результата:
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
Текущие две ветви объединяются в одну:
// До
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 }. Внутреннее представление меняется, видимое поведение для пользователя остаётся неизменным.
Новый синтаксис естественно доступен:
spawn while has_next() {
item = next()
process(item)
}
spawn if use_cache {
load_from_cache(key)
} else {
fetch(key)
}Компромиссы
Преимущества
- Синтаксическая ортогональность:
spawn+ любая управляющая конструкция = естественная параллельная комбинация - Всё является типом: система типов полностью записывает вычислительную структуру, compile-time рефлексия получает параллельную топологию
- Устранение особых случаев: удаление
Expr::SpawnForи связанного кода特殊ной обработки - Расширяемость: будущие новые управляющие конструкции автоматически комбинируются с
spawn,无需修改 spawn логики
Недостатки
- Раздувание системы типов: 6 новых вариантов
MonoType, увеличение сложности проверки типов - Нарушающее изменение: внутренние AST/IR представления меняются, нужно обновить весь код, потребляющий
Expr::SpawnFor - Вывод типов выражений: каждое выражение теперь должно возвращать тип вычислительной структуры, большой охват изменений
Альтернативные решения
| Решение | Почему не выбрано |
|---|---|
Сохранить 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 (пройдено):
items = [1, 2, 3, 4, 5]
results = spawn for item in items { item * 2 }
// Тип: Spawn(ForExpr { body_ty: List(Int) })- Свободные переменные:
item(локальная циклу, независимая копия каждой итерации),items(внешняя, только чтение в body) - Классификация эффектов:全部 Read(Local) или Read(Shared), без записей
- Доказано ✓
Сценарий 2 — Только чтение shared (пройдено):
config = load_config()
results = spawn for item in items { process(item, config) }
// Тип: Spawn(ForExpr { body_ty: List(Result) })- Свободные переменные:
item(Read(Local)),config(внешняя, без пути записи в body → Read(Shared)) - Классификация эффектов:全部 только чтение
- Доказано ✓
Сценарий 3 — Конфликт записи (отклонено):
mut counter = 0
spawn for item in items { counter += 1 }- Свободные переменные:
item(Read(Local)),counter(внешняя,+=десахаризуется в запись) - Классификация эффектов:
counter— Write(Shared), межитерационная запись в одну память - Экземпляр конфликта:
Write(task_0, counter) ∧ Write(task_1, counter) = True - Опровергнуто ✗ → Компиляционная ошибка:
Ошибка: межитерационный конфликт записи в теле spawn for. Переменная counter записывается несколькими параллельными задачами.
Сценарий 4 — while + stateful итератор (предупреждение/отклонение):
spawn while iter.has_next() {
item = iter.next()
process(item)
}
// Тип: Spawn(WhileExpr { body_ty: List(Processed) })- Свободные переменные:
iter(внешняя,next()→&mut self→&mut(Shared)) next()модифицирует состояние итератора, итерация N+1 зависит от побочного эффекта итерации N- Это не независимая задача → нарушение ограничения независимости
Spawn(WhileExpr) - Компилятор сообщает о межитерационной каузальной зависимости, рекомендует использовать
spawn for
Сценарий 5 — spawn if (пройдено):
result = spawn if use_cache { load(key) } else { fetch(key) }
// Тип: Spawn(IfExpr { then_ty: T, else_ty: Option(T) })- Выполняется только одна ветвь, межзадачных конфликтов нет
- Если в body есть вложенный spawn — рекурсивная проверка
- Доказано ✓
Сценарий 6 — spawn block межзадачные зависимости (DAG + верификация провайдера):
spawn {
a = fetch_user(id)
b = fetch_orders(a.user_id) // зависит от a
c = compute_stats() // независим
}
// Тип: Spawn(Block(Tuple(User, Orders, Stats)))- DAG-анализ:
aиcнезависимы (могут быть параллельны),bзависит отa(планируется после a) - Верификация провайдера: вход
b(a.user_id) вычислен до запуска b - Доказано ✓
Что НЕ делает MonoType
| Делает | Не делает |
|---|---|
| Идентифицирует форму утверждения | Не выполняет доказательство |
| Записывает вычислительную структуру на уровне типов | Не заменяет DAG-анализ |
| Предоставляет типовой ввод для провайдера RFC-027 | Не заменяет анализ свободных переменных, алиасинг, обнаружение конфликтов |
Реальную работу доказательства выполняют стандартные аналитические проходы компилятора. Ценность MonoType в том, что эти проходы调度ируются в единой типовой структуре — провайдеру верификации не нужно писать специальные ветки для каждого вида AST узла.
Стратегия реализации
Фазы
- AST + Parser:
Spawn { body: Box<Expr> }, удалениеSpawnFor - Система типов: новые 6 вариантов
MonoType, все выражения возвращают тип вычислительной структуры - Унификация DAG-анализа: объединение входов, унификация перечисления
TaskSource - Адаптация IR / Codegen: удаление
Ir::SpawnFor, унификация пути обработки - Упрощение Placement: удаление ветви
SpawnFor - Тестирование: все существующие тесты
spawn forпроходят
Область влияния
| Файл/директория | Изменения |
|---|---|
frontend/core/parser/ast.rs | Spawn 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 for | 2026-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 | Чётко определены обязательства по доказательству и условия отказа для каждого варианта MonoType | 2026-07-03 |
Ссылки
- RFC-024: Семантика параллельной среды выполнения на основе spawn
- RFC-010: Унифицированный синтаксис типов
- RFC-027: Compile-time предикаты и унифицированная статическая верификация
- RFC-019: Типоориентированная гомоиконность
- Спецификация модели параллелизма
- Обсуждение ортогональности spawn for (план)
Жизненный цикл и судьба
| Статус | Местоположение | Описание |
|---|---|---|
| На рассмотрении | docs/design/rfc/review/ | Открыто для обсуждения сообщества |
