Skip to content

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/Sync trait なし
  • Mutex/RwLock/Atomic なし
  • future/非同期ハンドルなし
  • ❌ 全プログラム DAG 分析なし
  • ❌ 関数着色なし(async/await)

ユーザーメンタルモデル:書いた普通のコードは順番に実行されます。複数のことを同時に行いたい時は、spawn <expr> の中に入れます。コールバックはなく、awaitはなく、おかしな注釈もありません。

設計の出典

ドキュメント関係
RFC-001本文に取って代わられる
RFC-008ランタイムアーキテクチャ、本質的に直交
RFC-009所有権モデル、変更なし
RFC-010統一型構文
RFC-032AST/IR 再構築、本文と協調して spawn を定義

動機

なぜこの設計が必要か?

現在の主流言語の並行モデルには明確な欠陥があります:

言語並行モデル問題
Rustasync/await + tokio非同期感染、関数着色、学習曲線が急
Gogoroutine型安全性がなく、データ競合の検出が難しい
PythonasyncioGIL 制限、関数着色
JavaScriptPromise/asyncコールバック地獄、関数着色

旧設計(RFC-001)の問題

RFC-001 で提唱された3層並行アーキテクチャ(L1/L2/L3)には以下の問題があります:

問題説明
メンタルモデルが複雑L1/L2/L3 三層抽象が追加の学習負担
注釈冗長@block/@eager/@auto 注釈でコードがうるさい
分析複雑度が高い全プログラム DAG 分析のコンパイル時間オーバーヘッド大
型制約が複雑Send/Sync trait が認知負荷を追加
制御不能自動並行動作が予測・ デバッグしにくい

設計目標

  1. シンプル:並行プリミティブは1つだけ(spawn)、任意の式を修飾可能
  2. 明示的:ユーザーがどこが並行、どこが順序を明確に把握
  3. 安全:所有権ルールが自然に延伸、追加の型制約不要
  4. 制御可能:暗黙の並行なし、予期せぬ並行動作なし
  5. 同期:呼び出し側は同期的にブロック、コールバックも await もない

提案

1. {} ブロックの本質:依存駆動の計算ユニット

YaoXiang では、{}依存駆動の計算ユニットです。

属性説明
依存駆動ブロックは実行時に内部の変数がすべて準備完了しているかチェック、準備完了なら即時実行、否则阻塞待機
実行タイミング依存関係で決まる、「即時」か「遅延」かは関係ない
戻り値return で明示的に値を返す;return がない場合はデフォルトで Void を返す
構文統一関数本体、変数初期化、spawn の後、どこに現れてもセマンティクスは一貫
スコープ隔離変数は厳密に {} 内部に制限され、外側のスコープには漏れない
yaoxiang
// 依存駆動の例
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 のルールを継承します。

yaoxiang
// 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 式は独立したスコープを作成し、内部の変数は外部に影響しません:

yaoxiang
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 セマンティクス):

yaoxiang
data = load_data()
result = spawn {
    process(data)       // data の所有権が spawn 式に移動
}
// data はここでは使用不可(move 済み)

複数のタスク間で共有する必要がある場合は、ref を使用します:

yaoxiang
data = load_data()
shared = ref data       // コンパイラが自動的に Rc または Arc を選択

result = spawn {
    process_a(shared),  // 共有参照
    process_b(shared)   // 共有参照
}

イテレーション間共有ref を使用して外側にキャプチャし、イテレーション間で同じ参照を共有します。

2.4 エラー伝播ルール

spawn { a, b, c }(ブロック)
  1. 全てのタスク完了を待つ(一部のタスクが失敗しても)
  2. 最初に遭遇したエラーを伝播
  3. ? を使用してエラー伝播ポイントを明示的にマーク
yaoxiang
(a, b) = spawn {
    fetch("url1")?,     // 失敗可能性あり
    fetch("url2")?      // 失敗可能性あり
}
// いずれかのタスクが失敗すると、spawn 式全体が最初のエラーを伝播
spawn for x in items { body? }
  • 全てのイテレーション完了後に最初のエラーを返す
  • 失敗したイテレーションの後、残りのイテレーションは実行継続(キャンセルなし)
  • ? を使用してエラー伝播ポイントを明示的にマーク
yaoxiang
results = spawn for item in items {
    process(item)?      // いずれかのイテレーションが失敗 → 全て完了を待つ → 最初のエラーを伝播
}
spawn while cond { body? }

while 自身のエラーセマンティクスを継承:

  • step が ? でエラーを伝播 → spawn while 全体が失敗、次のラウンドには入らない
  • step がエラーを伝播しない(エラーが飲み込まれる)→ 次のラウンドに進む
yaoxiang
spawn while has_next() {
    item = next()       // エラーを伝播しない場合、失敗しても次のラウンドに進む
    process(item)
}
spawn if c { a } else { b }
  • 条件 c は順序評価
  • c の評価でエラー → 全体がエラー
  • 選択されたブランチ内でエラー → 全体がエラー
yaoxiang
result = spawn if cond()? {  // cond は順序評価、失敗 → 全体がエラー
    fetch_a()?
} else {
    fetch_b()?
}

2.5 リソースタイプルール

コンパイラはリソースタイプの使用を追跡し、並行安全性を確保します:

リソースタイプ説明コンパイラの動作
FilePathファイルシステムパス同一パスへの操作は自動的に直列化
HttpUrlHTTP エンドポイント同一 URL への操作は自動的に直列化
DBUrlデータベース接続同一接続への操作は自動的に直列化
Console標準出力全ての Console 操作は自動的に直列化
spawn { ... } ブロック内
yaoxiang
// 同一ファイルへの操作は自動的に直列化
(a, b) = spawn {
    read_file("data.txt"),      // 先に実行
    write_file("data.txt", x)   // 読み取り完了を待つ
}
spawn for ... { ... } イテレーション間での同一リソース

全てのイテレーションが同一リソースタイプに操作する場合、コンパイラは自動的に直列に降格(spawn は順序的な for に退化、エラーなし):

yaoxiang
// 全イテレーションが同一ファイルパスに書き込み → 自動的に直列に降格
results = spawn for item in items {
    write_file("data.txt", item)
}
// コンパイラが全てのイテレーションを自動的に直列化

設計理由:spawn キーワードは並行意図を依然として表現します;リソース競合時にコンパイラが自動的に降格することは、直接拒否するより最小驚異の原則に従っています。

spawn while ... { ... } &mut のキャプチャ

コンパイル時エラーspawn while&mut 型の外部変数をキャプチャすることを許可しません:

yaoxiang
iter = make_iter()
spawn while iter.has_next() {       // コンパイル時エラー
    item = iter.next()              // iter は &mut、イテレーション間での可変共有 = データ競合
}

Sync trait を再導入しない:RFC-024 の「Send/Sync なし」という約束と一貫しています。ユーザーに ref または非 spawn 構文への変更を求めます。

spawn if c { ... } else { ... } 両ブランチでの同一リソース

合法で警告なし:if 条件は互いに排他的で、最大1つのブランチのみ実行され、並行競合は発生しません:

yaoxiang
result = spawn if use_cache {
    load_from_cache(key)            // ブランチ 1:cache を読み取る
} else {
    fetch(key)                      // ブランチ 2:URL を読み取る
}

2.6 ネスト spawn

spawn 式はネスト可能で、内側の spawn は独立した並行ドメインを作成します:

yaoxiang
(a, b) = spawn {
    x = spawn {
        fetch("url1"),
        fetch("url2")
    },
    y = compute(x)
}

ネストセマンティクス

  • 内側の spawn は独立した並行ドメイン(独立タスクキュー、独立エラー伝播)
  • 内側のエラーは独立して外側に伝播(外側タスクが内側完了を待つ時にエラーを受け取る)
  • 内側のリソースタイプルールは独立して追跡(外側との統合チェックなし)
yaoxiang
// 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/Atomicref が自動的に Rc/Arc を選択
L1/L2/L3 三層メンタルモデル普通のコードは順序、spawn 式は並行
関数着色(async/await)関数着色なし
spawn{} ブロックのみを修飾spawn は任意の式を修飾可能(RFC-032 参照)

4. 戻りルール

YaoXiang の戻りルールは一律で明確です:

書き方戻り値説明
= expr(波括弧なし)expr を直接返す式は値
= { ... }(波括弧あり)return が必要,否则返回 Voidブロックは明示的に返す必要がある
yaoxiang
// 波括弧なし:直接返す
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はなく、おかしな注釈もありません。

yaoxiang
// 普通のコード:順序実行
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. シンプル:並行プリミティブは1つだけ(spawn)、任意の式を修飾可能
  2. 明示的:ユーザーがどこが並行、どこが順序を明確に把握、暗黙の並行なし
  3. 安全:所有権ルールが自然に延伸、Send/Sync などの追加型制約不要
  4. 制御可能:自動並行動作なし、予期せぬ並行問題を回避
  5. 同期:呼び出し側は同期的にブロック、コードの理解とデバッグが容易
  6. 関数着色なし:async/await の関数着色問題が存在しない
  7. コンパイル効率が良い:DAG 分析は spawn 式内に限定、コンパイル時間が制御可能
  8. 直交性:spawn は任意の制御フロー構造と自然に組み合わせ可能(RFC-032 参照)

デメリット

  1. 明示的な spawn が必要:自動並行化なし、ユーザーが手動で並行ポイントをマークする必要がある
  2. spawn 式内の DAG 分析:コンパイラは spawn 式内で依存関係分析を行う必要がある
  3. 旧コードとの非互換:旧 RFC-001 モードを使用しているコードは移行が必要

代替案

方案なぜ選択しないか
全プログラム自動 DAG(RFC-001)複雑度が高い、コンパイル時間が長い、動作が制御不能
async/await関数着色、学習曲線が急、コード可読性が悪い
goroutine型安全性がなく、データ競合の検出が難しい
Actor モデルメッセージ渡しが複雑、デバッグが困難
CSP(Go channel)型安全性がなく、デッドロックの検出が難しい
spawn{} ブロックのみを修飾直交性を壊す、spawn for が特例になる(RFC-032 参照)

実装策略

コンパイル時分析

  1. 式形状認識:spawn 後の式の形状に応じてタスク分解を決定(RFC-032 §DAG 分析参照)
  2. DAG 構築:spawn 式内の依存関係を分析
  3. トポロジカルソート:spawn 式内の実行順序を決定
  4. 並行識別:spawn 式内の依存関係のないサブツリーを識別
  5. エスケープ分析ref → Rc と Arc のどちらを選択するか
  6. リソース競合検出:リソースタイプの潜在的な競合を検出

モジュール構成

spawn 関連コードは統一して frontend/core/spawn/ に配置:

frontend/core/spawn/
├── mod.rs           # spawn モジュールエントリ
├── placement.rs     # spawn 出現位置の合法性チェック
└── analysis.rs      # タスク識別、依存関係分析、リソース競合検出

移行説明(2026-06-11):既存の frontend/core/typecheck/passes/spawn_placement.rsfrontend/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 公式ドキュメント

外部参照


ライフサイクルと行き先

状態位置説明
承認済み(改訂版)docs/design/rfc/accepted/RFC-032 と協調して spawn を定義(ランタイムセマンティクス)