RFC-024:spawn を基にした並行ランタイムセマンティクス
本文書は
spawnのランタイム動作セマンティクスを定義します。構文の直交性、AST/IR 再構築、型システム拡張については RFC-032 を参照してください。2つの RFC が協調して
spawnを定義します——024 は「何をするか」に答え、032 は「どう表現するか」に答えます。
参照:
概要
本文書は YaoXiang プログラミング言語の spawn のランタイム動作セマンティクスを定義します:spawn <expr> は唯一の並行プリミティブであり、任意の式を修飾できます。呼び出し側は同期的にブロックします。式の形状が決めたタスク分解粒度に応じて、ランタイムは GMPモデルでスケジュールします——依存のないタスクはワークキューに投入され、worker が取り合って実行します。
コア設計——1つのプリミティブ、1組のルール:
spawn <expr> ← 唯一の並行プリミティブ
タスク分解は式の形状で決まる ← 唯一のルール
同期的にブロックして結果を待つ ← 唯一の動作排除した複雑さ:
- ❌
@block/@eager/@auto注釈なし - ❌
Send/Synctrait なし - ❌
Mutex/RwLock/Atomicなし - ❌
future/非同期ハンドルなし - ❌ 全プログラム DAG 分析なし
- ❌ 関数着色なし(async/await)
ユーザーメンタルモデル:書いた普通のコードは順番に実行されます。複数のことを同時に行いたい時は、
spawn <expr>の中に入れます。コールバックはなく、awaitはなく、おかしな注釈もありません。
設計の出典
| ドキュメント | 関係 |
|---|---|
| RFC-001 | 本文に取って代わられる |
| RFC-008 | ランタイムアーキテクチャ、本質的に直交 |
| RFC-009 | 所有権モデル、変更なし |
| RFC-010 | 統一型構文 |
| RFC-032 | AST/IR 再構築、本文と協調して spawn を定義 |
動機
なぜこの設計が必要か?
現在の主流言語の並行モデルには明確な欠陥があります:
| 言語 | 並行モデル | 問題 |
|---|---|---|
| Rust | async/await + tokio | 非同期感染、関数着色、学習曲線が急 |
| Go | goroutine | 型安全性がなく、データ競合の検出が難しい |
| Python | asyncio | GIL 制限、関数着色 |
| JavaScript | Promise/async | コールバック地獄、関数着色 |
旧設計(RFC-001)の問題
RFC-001 で提唱された3層並行アーキテクチャ(L1/L2/L3)には以下の問題があります:
| 問題 | 説明 |
|---|---|
| メンタルモデルが複雑 | L1/L2/L3 三層抽象が追加の学習負担 |
| 注釈冗長 | @block/@eager/@auto 注釈でコードがうるさい |
| 分析複雑度が高い | 全プログラム DAG 分析のコンパイル時間オーバーヘッド大 |
| 型制約が複雑 | Send/Sync trait が認知負荷を追加 |
| 制御不能 | 自動並行動作が予測・ デバッグしにくい |
設計目標
- シンプル:並行プリミティブは1つだけ(
spawn)、任意の式を修飾可能 - 明示的:ユーザーがどこが並行、どこが順序を明確に把握
- 安全:所有権ルールが自然に延伸、追加の型制約不要
- 制御可能:暗黙の並行なし、予期せぬ並行動作なし
- 同期:呼び出し側は同期的にブロック、コールバックも
awaitもない
提案
1. {} ブロックの本質:依存駆動の計算ユニット
YaoXiang では、{} は依存駆動の計算ユニットです。
| 属性 | 説明 |
|---|---|
| 依存駆動 | ブロックは実行時に内部の変数がすべて準備完了しているかチェック、準備完了なら即時実行、否则阻塞待機 |
| 実行タイミング | 依存関係で決まる、「即時」か「遅延」かは関係ない |
| 戻り値 | return で明示的に値を返す;return がない場合はデフォルトで Void を返す |
| 構文統一 | 関数本体、変数初期化、spawn の後、どこに現れてもセマンティクスは一貫 |
| スコープ隔離 | 変数は厳密に {} 内部に制限され、外側のスコープには漏れない |
// 依存駆動の例
x = compute_x() // x が準備完了
y = compute_y() // y が準備完了
result = {
// x と y に依存、両方準備完了後即時実行
return x + y
}2. spawn 式セマンティクス
spawn <expr> は YaoXiang の唯一の並行プリミティブです。任意の式を修飾可能で、式の形状が決めたタスク分解粒度に応じます。
2.1 タスク作成ルール
| 式の形状 | タスク分解 | 同期セマンティクス |
|---|---|---|
spawn { a, b, c } | 直接部分式 → N 個の独立タスク | 全てのタスク完了を待つ |
spawn for x in items { body } | 各イテレーション → 1 個のタスク | 全てのイテレーション完了を待つ |
spawn while cond { body } | 各ラウンド → 1 個のタスク(イテレーション間の条件駆動) | 条件が false になるまで待つ |
spawn if c { a } else { b } | 条件 c は順序評価、選択されたブランチ全体が → 1 個のタスク | 選択されたブランチ完了を待つ |
spawn call(x) | 呼び出し自体 → 1 個のタスク | 呼び出し完了を待つ |
spawn expr(任意の式) | 式自体 → 1 個のタスク | 式完了を待つ |
設計動機:なぜ spawn は任意の式を修飾できるのか?詳細は RFC-032 §コア設計 を参照。
制御フロー直交性:
spawn <expr>(spawn が前)と<expr> spawn { body }(spawn が後)のセマンティクスの差異については RFC-032 §制御フロー直交性(コア定義)を参照。(for ... spawn { }/while ... spawn { }/if ... spawn { }のような)逆順の全ての組み合わせのランタイム動作——エラー伝播リソースタイプ、ネストルール——は本文の §2.4 / §2.5 / §2.6 のルールを継承します。
// spawn ブロック:直接部分式が並行
(a, b) = spawn {
t1 = fetch("url1") // 直接部分式 → 並行タスク 1
t2 = fetch("url2") // 直接部分式 → 並行タスク 2
return (t1, t2) // 明示的にタプルを返す
}
// spawn for:各イテレーションが並行
results = spawn for item in items {
process(item) // 各イテレーション → 独立タスク
}
// spawn while:各ラウンドが並行
spawn while has_next() {
step() // 各ラウンド → 独立タスク
}
// spawn if:選択されたブランチ全体がタスク
result = spawn if cond {
branch_a()
} else {
branch_b()
}2.2 スコープ隔離
spawn 式は独立したスコープを作成し、内部の変数は外部に影響しません:
x = 10
result = spawn {
x = 20 // これは spawn 式内の局所的な x
compute(x)
}
// x は依然として 10
result = spawn for item in items {
item = item + 1 // イテレーション局所的な item、各イテレーションで独立コピー
process(item)
}
// 外側の item は影響されないイテレーション変数(for の x)は各ラウンドで独立コピー、イテレーション終了時に自動的に破棄されます。
2.3 所有権ルール
変数が spawn 式に入ると、外部ではもう使用できません(Move セマンティクス):
data = load_data()
result = spawn {
process(data) // data の所有権が spawn 式に移動
}
// data はここでは使用不可(move 済み)複数のタスク間で共有する必要がある場合は、ref を使用します:
data = load_data()
shared = ref data // コンパイラが自動的に Rc または Arc を選択
result = spawn {
process_a(shared), // 共有参照
process_b(shared) // 共有参照
}イテレーション間共有:ref を使用して外側にキャプチャし、イテレーション間で同じ参照を共有します。
2.4 エラー伝播ルール
spawn { a, b, c }(ブロック)
- 全てのタスク完了を待つ(一部のタスクが失敗しても)
- 最初に遭遇したエラーを伝播
?を使用してエラー伝播ポイントを明示的にマーク
(a, b) = spawn {
fetch("url1")?, // 失敗可能性あり
fetch("url2")? // 失敗可能性あり
}
// いずれかのタスクが失敗すると、spawn 式全体が最初のエラーを伝播spawn for x in items { body? }
- 全てのイテレーション完了後に最初のエラーを返す
- 失敗したイテレーションの後、残りのイテレーションは実行継続(キャンセルなし)
?を使用してエラー伝播ポイントを明示的にマーク
results = spawn for item in items {
process(item)? // いずれかのイテレーションが失敗 → 全て完了を待つ → 最初のエラーを伝播
}spawn while cond { body? }
while 自身のエラーセマンティクスを継承:
- step が
?でエラーを伝播 → spawn while 全体が失敗、次のラウンドには入らない - step がエラーを伝播しない(エラーが飲み込まれる)→ 次のラウンドに進む
spawn while has_next() {
item = next() // エラーを伝播しない場合、失敗しても次のラウンドに進む
process(item)
}spawn if c { a } else { b }
- 条件 c は順序評価
- c の評価でエラー → 全体がエラー
- 選択されたブランチ内でエラー → 全体がエラー
result = spawn if cond()? { // cond は順序評価、失敗 → 全体がエラー
fetch_a()?
} else {
fetch_b()?
}2.5 リソースタイプルール
コンパイラはリソースタイプの使用を追跡し、並行安全性を確保します:
| リソースタイプ | 説明 | コンパイラの動作 |
|---|---|---|
FilePath | ファイルシステムパス | 同一パスへの操作は自動的に直列化 |
HttpUrl | HTTP エンドポイント | 同一 URL への操作は自動的に直列化 |
DBUrl | データベース接続 | 同一接続への操作は自動的に直列化 |
Console | 標準出力 | 全ての Console 操作は自動的に直列化 |
spawn { ... } ブロック内
// 同一ファイルへの操作は自動的に直列化
(a, b) = spawn {
read_file("data.txt"), // 先に実行
write_file("data.txt", x) // 読み取り完了を待つ
}spawn for ... { ... } イテレーション間での同一リソース
全てのイテレーションが同一リソースタイプに操作する場合、コンパイラは自動的に直列に降格(spawn は順序的な for に退化、エラーなし):
// 全イテレーションが同一ファイルパスに書き込み → 自動的に直列に降格
results = spawn for item in items {
write_file("data.txt", item)
}
// コンパイラが全てのイテレーションを自動的に直列化設計理由:spawn キーワードは並行意図を依然として表現します;リソース競合時にコンパイラが自動的に降格することは、直接拒否するより最小驚異の原則に従っています。
spawn while ... { ... } &mut のキャプチャ
コンパイル時エラー:spawn while は &mut 型の外部変数をキャプチャすることを許可しません:
iter = make_iter()
spawn while iter.has_next() { // コンパイル時エラー
item = iter.next() // iter は &mut、イテレーション間での可変共有 = データ競合
}
Synctrait を再導入しない:RFC-024 の「Send/Sync なし」という約束と一貫しています。ユーザーにrefまたは非 spawn 構文への変更を求めます。
spawn if c { ... } else { ... } 両ブランチでの同一リソース
合法で警告なし:if 条件は互いに排他的で、最大1つのブランチのみ実行され、並行競合は発生しません:
result = spawn if use_cache {
load_from_cache(key) // ブランチ 1:cache を読み取る
} else {
fetch(key) // ブランチ 2:URL を読み取る
}2.6 ネスト spawn
spawn 式はネスト可能で、内側の spawn は独立した並行ドメインを作成します:
(a, b) = spawn {
x = spawn {
fetch("url1"),
fetch("url2")
},
y = compute(x)
}ネストセマンティクス:
- 内側の spawn は独立した並行ドメイン(独立タスクキュー、独立エラー伝播)
- 内側のエラーは独立して外側に伝播(外側タスクが内側完了を待つ時にエラーを受け取る)
- 内側のリソースタイプルールは独立して追跡(外側との統合チェックなし)
// spawn for 内に spawn while をネスト
results = spawn for x in items {
inner = spawn while has_more(x) {
step(x)
}
process(inner)
}3. 旧設計との決別
| 旧設計(RFC-001) | 新設計(RFC-024 + RFC-032) |
|---|---|
| 全プログラム自動 DAG 分析 | spawn 式内のみ分析 |
@block/@eager/@auto 注釈 | 注釈なし、依存駆動 |
Send/Sync trait | 不要、所有権 + ref が自動的に処理 |
future/非同期ハンドル | 同期ブロック、コールバックなし |
Mutex/RwLock/Atomic | ref が自動的に Rc/Arc を選択 |
| L1/L2/L3 三層メンタルモデル | 普通のコードは順序、spawn 式は並行 |
| 関数着色(async/await) | 関数着色なし |
spawn は {} ブロックのみを修飾 | spawn は任意の式を修飾可能(RFC-032 参照) |
4. 戻りルール
YaoXiang の戻りルールは一律で明確です:
| 書き方 | 戻り値 | 説明 |
|---|---|---|
= expr(波括弧なし) | expr を直接返す | 式は値 |
= { ... }(波括弧あり) | return が必要,否则返回 Void | ブロックは明示的に返す必要がある |
// 波括弧なし:直接返す
add: (a: Int, b: Int) -> Int = a + b
// 波括弧あり:`return` が必要
process: (data: Data) -> Result = {
validated = validate(data)?
return ok(transform(validated))
}
// 波括弧ありだが `return` なし:`Void` を返す
log: (message: String) -> Void = {
print(message) // `return` なし、`Void` を返す
}5. ユーザーメンタルモデル
書いた普通のコードは順番に実行されます。
複数のことを同時に行いたい時は、
spawn <expr>の中に入れます。式の形状が決めたタスク分解方法:ブロック内の各直接部分式は並行;for の各イテレーションは並行;if の選択されたブランチは1つのタスクとして実行。
spawn 式全体が同期的にブロックし、全てのタスク完了を待ちます。
コールバックはなく、
awaitはなく、おかしな注釈もありません。
// 普通のコード:順序実行
a = compute_a() // 先に実行
b = compute_b(a) // a に依存、a 完了後に実行
c = compute_c(b) // b に依存、b 完了後に実行
// 並行が必要な時:spawn を使用
(x, y, z) = spawn {
fetch("url1"), // 並行
fetch("url2"), // 並行
fetch("url3") // 並行
}
// 全て完了後に続行
process(x, y, z)
// データ並行:spawn for
results = spawn for item in items {
process(item)
}トレードオフ
メリット
- シンプル:並行プリミティブは1つだけ(
spawn)、任意の式を修飾可能 - 明示的:ユーザーがどこが並行、どこが順序を明確に把握、暗黙の並行なし
- 安全:所有権ルールが自然に延伸、
Send/Syncなどの追加型制約不要 - 制御可能:自動並行動作なし、予期せぬ並行問題を回避
- 同期:呼び出し側は同期的にブロック、コードの理解とデバッグが容易
- 関数着色なし:async/await の関数着色問題が存在しない
- コンパイル効率が良い:DAG 分析は spawn 式内に限定、コンパイル時間が制御可能
- 直交性:spawn は任意の制御フロー構造と自然に組み合わせ可能(RFC-032 参照)
デメリット
- 明示的な spawn が必要:自動並行化なし、ユーザーが手動で並行ポイントをマークする必要がある
- spawn 式内の DAG 分析:コンパイラは spawn 式内で依存関係分析を行う必要がある
- 旧コードとの非互換:旧 RFC-001 モードを使用しているコードは移行が必要
代替案
| 方案 | なぜ選択しないか |
|---|---|
| 全プログラム自動 DAG(RFC-001) | 複雑度が高い、コンパイル時間が長い、動作が制御不能 |
| async/await | 関数着色、学習曲線が急、コード可読性が悪い |
| goroutine | 型安全性がなく、データ競合の検出が難しい |
| Actor モデル | メッセージ渡しが複雑、デバッグが困難 |
| CSP(Go channel) | 型安全性がなく、デッドロックの検出が難しい |
spawn は {} ブロックのみを修飾 | 直交性を壊す、spawn for が特例になる(RFC-032 参照) |
実装策略
コンパイル時分析
- 式形状認識:spawn 後の式の形状に応じてタスク分解を決定(RFC-032 §DAG 分析参照)
- DAG 構築:spawn 式内の依存関係を分析
- トポロジカルソート:spawn 式内の実行順序を決定
- 並行識別:spawn 式内の依存関係のないサブツリーを識別
- エスケープ分析:
ref→ Rc と Arc のどちらを選択するか - リソース競合検出:リソースタイプの潜在的な競合を検出
モジュール構成
spawn 関連コードは統一して frontend/core/spawn/ に配置:
frontend/core/spawn/
├── mod.rs # spawn モジュールエントリ
├── placement.rs # spawn 出現位置の合法性チェック
└── analysis.rs # タスク識別、依存関係分析、リソース競合検出移行説明(2026-06-11):既存の
frontend/core/typecheck/passes/spawn_placement.rsはfrontend/core/spawn/placement.rsに移行します。typecheck/passes/ディレクトリ内のspawn_placementモジュール宣言は同期的に削除する必要があります。
ランタイム実行
RFC-008 の Runtime アーキテクチャを参照:
- Embedded Runtime:spawn サポートなし、即時実行
- Standard Runtime:spawn 式をサポート
- Full Runtime:Standard + WorkStealer 負荷分散
依存関係
- RFC-008(Runtime アーキテクチャ)→ 完了
- RFC-009(所有権モデル)→ 完了
- RFC-010(統一型構文)→ 完了
- RFC-011(ジェネリックシステム)→ 完了
- RFC-032(AST/IR 再構築)→ 本文と協調して spawn を定義
設計意思決定記録
| 意思決定 | 決定 | 理由 | 日付 |
|---|---|---|---|
| 並行プリミティブ | spawn <expr> | シンプル、明示的、制御可能 | 2026-06-05 |
| spawn 修飾範囲 | 任意の式 | 構文直交、spawn for 特殊化を排除 | 2026-07-04 |
| タスク分解 | 式の形状で決まる | 表現力が高い、ルールが統一 | 2026-07-04 |
| 実行モデル | 同期ブロック | 理解しやすい、デバッグしやすい | 2026-06-05 |
| DAG 分析範囲 | spawn 式内のみ | コンパイル効率が良い、動作が制御可能 | 2026-06-05 |
| 共有メカニズム | ref が自動的に Rc/Arc を選択 | ユーザーの意思決定を簡略化 | 2026-06-05 |
| 注釈 | なし | コードノイズを減らす | 2026-06-05 |
| Send/Sync | 削除 | 所有権 + ref で十分 | 2026-06-05 |
| Mutex/RwLock | 削除 | ref が自動的に処理 | 2026-06-05 |
| future/ハンドル | 削除 | 同期ブロックの方がシンプル | 2026-06-05 |
| 関数着色 | なし | async/await 問題を回避 | 2026-06-05 |
| リソースタイプ | 組み込み + ユーザー定義 | 自動直列化 | 2026-06-05 |
spawn {} エラー | 全て完了を待ち、最初のエラーを伝播 | 決定論的動作 | 2026-06-05 |
spawn for エラー | 全て完了を待ち、最初のエラーを伝播 | spawn {} と一貫 | 2026-07-04 |
spawn while エラー | while エラーセマンティクスを継承 | while 標準動作 | 2026-07-04 |
spawn if 条件エラー | c は順序評価、失敗 → 全体エラー | 直感に反しない | 2026-07-04 |
spawn for 同一リソース | 自動的に直列に降格 | 安全な降格、粗暴な拒否ではない | 2026-07-04 |
spawn while &mut キャプチャ | コンパイル時エラー | データ競合を回避、Sync を導入しない | 2026-07-04 |
spawn if 同一リソース | 合法で警告なし | 排他的ブランチは競合を構成しない | 2026-07-04 |
| ネスト spawn | 内側が独立並行ドメイン | 独立タスクキュー、エラー、リソース | 2026-07-04 |
参考文献
YaoXiang 公式ドキュメント
- 並行モデル仕様
- RFC-001 並行モデル(廃止)
- RFC-008 Runtime 並行モデル
- RFC-009 所有権モデル
- RFC-010 統一型構文
- RFC-011 ジェネリックシステム
- RFC-032 spawn 統一式修飾 — AST/IR 再構築
外部参照
ライフサイクルと行き先
| 状態 | 位置 | 説明 |
|---|---|---|
| 承認済み(改訂版) | docs/design/rfc/accepted/ | RFC-032 と協調して spawn を定義(ランタイムセマンティクス) |
