Skip to content

RFC-009: 所有権モデルの設計 ​

要約 ​

本文書は YaoXiang プログラミング言語の**所有権モデル(Ownership Model)**を定義する。

核心設計——5 つの概念、1 つのグラデーション:

ちら見/その場変更     奪う           共有保持          複製する         システムレベル
    │              │              │              │              │
   &T            Move           ref          clone()        unsafe
  &mut T         ゼロコピー      コンパイラ自動     明示的ディープコピー    *T
  ゼロサイズトークン デフォルト    Rc/Arc 選択                    ユーザ責任
  型属性自然
  権限を推論
  • Move(デフォルト):代入/引数渡し/戻り値 = 所有権移転、ゼロコピー、RAII による自動解放
  • &T / &mut T(借用トークン):ゼロサイズのコンパイル時トークン型。&T は複製可能(共有読み取り)、&mut T は線形(排他的書き込み)。権限は型属性から自然に推論され、特別なルールは不要。戻り値や構造体フィールドへの格納も可能。
  • ref キーワード:スコープを跨いだ共有。コンパイラが Rc(タスクを跨がない)か Arc(タスクを跨ぐ)を自動選択
  • clone():明示的なディープコピー
  • unsafe + *T:生ポインタ、システムレベルのエスケープハッチ

除去された複雑性:

  • ❌ ライフタイム 'a なし
  • ❌ 独立した借用検査フレームワークなし(借用衝突はホーア命題に次元削減され、型検査と証明パイプラインを共有)
  • ❌ GC なし
  • ❌「エスケープ禁止」のような特別ルールなし(トークンは通常の型であり、スコープは型システムで統一的に処理)
  • ❌ ユーザは Rc/Arc の違いを知る必要がない(コンパイラが自動選択)

プログラミング負担:&T は複製可能、&mut T は複製不可——2 つの型属性、0 つの特別ルール、コンパイラは完全自動。 性能保証:Move はゼロオーバーヘッド、トークンはゼロオーバーヘッド(ゼロサイズ型、コンパイル後に消滅)、ref は必要に応じたコスト、GC による一時停止なし。

動機 ​

なぜ所有権モデルが必要か? ​

言語メモリ管理問題
C/C++手動管理メモリリーク、ダングリングポインタ、二重解放
Java/PythonGCレイテンシ変動、メモリオーバーヘッド、予測不能な停止
Rust所有権 + 借用検査ライフタイム 'a の学習曲線が急峻
YaoXiangMove + Token + refシンプルで確定的な GC なし

設計目標 ​

yaoxiang
# 1. デフォルトは Move(ゼロコピー)
p = Point(1.0, 2.0)
p2 = p                         # Move、p は以降読めない

# 2. &T / &mut T 借用トークン(ゼロオーバーヘッド、型属性から権限を自然に推論)
print_info(p2)                 # コンパイラが &Point トークンを自動生成し、使用後解放
shift(p2, 1.0, 1.0)           # コンパイラが &mut Point トークンを自動生成

# 3. ref = 共有(コンパイラが Rc/Arc を自動選択)
shared = ref p2                # スコープを跨いで保持
spawn { use(shared) }          # コンパイラ:タスクを跨ぐ → Arc

# 4. clone() = 明示的コピー
backup = p2.clone()            # ディープコピー、独立

# 5. unsafe + *T = システムレベル
unsafe {
    ptr: *Point = &p
    (*ptr).x = 0.0
}

Rust との核心的な違い ​

特性RustYaoXiang
デフォルトのセマンティクス借用 &T(明示的に .clone() が必要)Move(値渡し、ゼロコピー)
借用&T/&mut T、返却可能、ライフタイムが必要&T/&mut T ゼロサイズトークン、Dup/Linear 型属性から自然に権限を推論
共有メカニズムArc::new() + 手動 Weakref キーワード(コンパイラが Rc/Arc を自動選択)
コピーclone()clone()
生ポインタ*T*T
ライフタイム'a❌ なし
借用検査グローバルな推論型検査器が借用命題を自動生成し、証明パイプラインで統一検証
循環参照手動 Weakタスク終了時に統一解放 / タスク跨ぎは lint / 標準ライブラリ Weak

提案 ​

1. Move(デフォルトの所有権移転) ​

yaoxiang
# ルール:代入 / 引数渡し / 戻り値 = Move、ゼロコピー

p: Point = Point(1.0, 2.0)
p2 = p                           # Move、p は以降読めない

# 変数は再代入可能(Python 風、シャドウイングなし)
p = Point(3.0, 4.0)              # p を再バインド、型は一致必須

# 関数引数:Move
process: (p: Point) -> Point = {
    p.transform()
    p                            # Move で返却
}

# 関数の戻り値:Move
create: () -> Point = {
    p = Point(1.0, 2.0)
    p                            # Move で返却、ゼロコピー
}

特徴:

  • ゼロコピー(コンパイラがポインタを移動)
  • 移動後、元のバインドは読めない(コンパイルエラー)
  • RAII:スコープ終了時に自動解放
  • 関数シグネチャ (T) -> T 自体がドキュメント——T を消費し、T を返す

2. &T / &mut T(借用トークン) ​

核心原則:&T と &mut T はゼロサイズのコンパイル時トークン型である。これらは「参照」ではなく、「アクセス権限の型レベル証明」である。

2.1 2 つの型属性 ​

&T      →  ゼロサイズ、ソースデータを凍結(ReadToken 生存中は WriteToken を禁止)、
          凍結保証のもとで複数の読み取り専用ビューが安全 → 複製可能(Dup)
&mut T  →  ゼロサイズ、排他的読み書き(WriteToken 生存中は他のあらゆるトークンを禁止)、
          排他的アクセス下では複製は無意味 → 線形(non-Dup)

因果関係を逆転させてはならない:凍結が原因、Dup は結果。 &T が Dup を実装しているから共存できる——ということではない。データが凍結されている(変異可能性がない)からこそ、複数の読み取り専用ビューが安全であり、Dup が実現可能なのである。Dup を定義とし、衝突検査を「追加のパッチ」と捉えると、設計を誤る。

2.2 基本使用方法 ​

yaoxiang
# メソッド側:引数型を宣言し、必要な権限を決定
Point.print: (self: &Point) -> Void = {
    print(self.x)                  # &Point トークンが読み取り権限を付与
    print(self.y)
}

Point.shift: (self: &mut Point, dx: Float, dy: Float) -> Void = {
    self.x = self.x + dx           # &mut Point トークンが書き込み権限を付与
    self.y = self.y + dy
}

# 呼び出し側:コンパイラが借用と Move を自動選択
p = Point(1.0, 2.0)
p.print()                          # コンパイラが &Point トークンを自動生成
p.shift(1.0, 1.0)                  # コンパイラが &mut Point トークンを自動生成
p.print()                          # OK、shift 呼び出し終了で前のトークンは解放済み

# 自由関数も同様
distance: (a: &Point, b: &Point) -> Float = {
    sqrt((a.x - b.x)**2 + (a.y - b.y)**2)  # 2 つの &Point トークンが共存——Dup 型
}
d = distance(p, p2)

2.3 なぜ「エスケープ禁止」が不要か ​

RFC-009 v8 では &T/&mut T に対して 3 つの特別ルールを課していた——引数にのみ使用可、返却不可、構造体に格納不可。これは「借用」概念にパッチを当てていた。

トークンシステムにはこれらのルールは不要。トークンは通常の型であり、他のすべての型と同じスコープルールに従う。

参照の返却——自然にサポート:

yaoxiang
# ✅ トークンは戻り値とともに伝播
Point.get_x: (self: &Point) -> (&Float, &Point) = {
    return (&self.x, self)  # 子トークンと親トークンを一緒に返却
}

# 使用
p = Point(1.0, 2.0)
(px_ref, p) = p.get_x()    # トークンが呼び出し元に返却される
print(px_ref)               # OK、トークンはまだスコープ内

構造体への格納——自然にサポート:

yaoxiang
# ✅ 構造体はトークンをフィールドとして持てる
Window: Type = {
    target: Point,
    view: &Point,      # トークンフィールド——target への読み取り専用ビューを保持
}

# view のトークンは target から派生し、Window は両方の所有権を保持
# Window が存在する限り、view トークンは有効

2.3 クロージャと Lambda の明示的な引数 ​

Lambda は関数値——返却、格納、現在のスコープ外への持ち出しが可能。したがって Lambdaは外側のローカル変数を暗黙的にキャプチャしない。外側のデータが必要な場合は、明示的な引数として渡す。

yaoxiang
# ✅ Lambda は明示的な引数を使用
double: (x: Int) -> Int = (x) => x * 2
filter_by: (items: List(Int), f: (Int) -> Bool) -> List(Int) = { ... }

# ✅ spawn { } はこのルールの影響を受けない——spawn は即座に実行される並列ブロックで、親タスクは完了を待機
shared = ref data
spawn { use(shared) }

# ❌ Lambda は外側の変数を暗黙的にキャプチャできない
x = 42
f = () => { x + 1 }  # コンパイルエラー:x はスコープ外

# ✅ 正しい方法:明示的に引数で渡す
f = (x) => { x + 1 }
f(x)

# ✅ 正しい方法その 2:コンテキストを作成時点で固定(カリー化)——クロージャは引数のみを取り、キャプチャしない
gt: (t: Int) -> (x: Int) -> Bool = (x) => x > t
evens = list.filter(nums, gt(threshold))

補足(2026-08-17):コンテキスト依存の正解はカリー化による固定であり、キャプチャではない。クロージャがエスケープした後、その定義箇所のスコープは既に死んでいる可能性があるため、暗黙的にキャプチャしてはならない。しかし呼び出し時点(作成時点)のスコープは必ず生存しており、コンテキストはその時点で値としてクロージャ内に固定されるのが安全である。SPEC §12.3 を参照。

spawn { } は関数値ではない。 spawn でマークされたブロックは if/while 本体と同様に——即座に実行され、親スタックフレームが生存中に完了する。spawn 本体は外側の変数に通常通りアクセス可能。

タスクを跨ぐ——トークンはスレッドを跨げない:

yaoxiang
# ❌ トークンはタスク境界を跨げない
bad_task: (p: &Point) -> Void = {
    spawn { print(p.x) }          # ❌ コンパイルエラー:トークンはタスクを跨いで渡せない
}

# これは特別ルールではない——トークンはコンパイル時の権限証明であり、タスクを跨いだ共有は ref を使用
# タスクを跨いだ共有が必要な場合は、ref を使用

トークンは ref できない:

yaoxiang
# ❌ トークンは権限証明であり、所有権ではない
bad_ref: (p: &Point) -> Void = {
    shared = ref p                # ❌ コンパイルエラー:&T は所有可能な型ではない
}

2.4 トークンのライフタイム ​

トークンのライフタイムは通常のスコープルールによって決定され、ライフタイムパラメータは不要。

  • 関数引数内のトークン:呼び出し中に生存し、呼び出し終了後に解放
  • 返却されたトークン:所有権を呼び出し元に移転
  • 構造体に格納されたトークン:構造体とともに生存

コンパイラは 'a アノテーションを必要としない。なぜならトークンは値であり、値のライフタイムは所有権システム(Move/RAII)によって統一的に管理されるからだ。借用問題を所有権問題に次元削減する。

2.5 トークン衝突検出 ​

トークン衝突検出はホーア論理命題であり、独立したフロー依存解析ではない。

{衝突する ReadToken がすべて死亡} write(data) {WriteToken は安全に取得可能}

型検査やユーザ述語検証と RFC-027 の証明パイプラインを共有する。コンパイラが借用命題(borrow_conflict、use_after_move、use_after_drop、mut_violation)を自動生成し、パイプラインに投入して検証する。パイプラインは Proved / Disproved / Unproven を返す。

yaoxiang
# ❌ &mut トークンは線形であり、複製不可
bad_dup: (p: &mut Point) -> Void = {
    p2: &mut Point = p              # Move、p は以降読めない
    p.x = 10.0                      # ❌ コンパイルエラー:WriteToken は既に移動済み
}

# ✅ &T トークンは Dup 型であり、自由に複製可能
good_dup: (p: &Point) -> Void = {
    p2: &Point = p                  # OK、&T は Dup 型
    print(p.x)                      # OK
    print(p2.x)                     # OK、2 つの読み取り専用トークンが共存
}

借用検査は消えていない——次元削減された。 既存の BorrowChecker は BorrowPredicateEmitter(命題生成器)となり、生成された借用命題は他の型命題と同じ証明パイプラインを共有する。これは型検査器の概念と完全に並行する:型検査器は型等価命題を生成し、借用命題生成器は借用命題を生成し、同一のパイプラインが検証する。詳細は RFC-009a を参照。

2.7 コンパイラ内部:ブランド機構 ​

ユーザがブランドに触れることは決してない。コンパイラは内部で各トークンにコンパイル時一意識別子を割り当てる。

ユーザに見えるもの          コンパイラ内部表現
────────────────────────────────────────
&Point         →  ReadToken(Point, #N)    // #N はコンパイル時一意な整数
&mut Point     →  WriteToken(Point, #M)   // #M はコンパイル時一意な整数

ブランドの用途:

  • 偽造防止:トークンは所有者のカプセルからのみ取得でき、凭空には構築できない
  • 関連追跡:&Point から &Float(フィールドアクセス)を派生する際、&Float は派生ブランド(#N.field_x)を運び、コンパイラは親トークンまで追跡可能
  • 衝突検出:同源の WriteToken と派生 ReadToken は同時にアクティブになれない

ブランドは単態化とインライン化後に完全に消滅し、生成される機械語には存在しない。ランタイムオーバーヘッドはゼロ。

2.8 自動借用選択ルール ​

呼び出し側コンパイラは以下の優先順位で自動選択する:

1. 実引数が後に使用される場合 → トークン生成を優先(&T または &mut T、メソッドシグネチャに従う)
2. 実引数が後に使用されない場合 → Move
3. 優先マッチング順序:&T < &mut T < Move
yaoxiang
# 例:自動選択
p = Point(1.0, 2.0)
p.print()        # print は &self を宣言 → コンパイラが &Point トークンを生成
p.shift(1.0, 1.0) # shift は &mut self を宣言 → コンパイラが &mut Point トークンを生成
p2 = p           # Move、p は以降使用されない

2.9 RFC-009 v8 簡素版借用との比較 ​

特性簡素版借用 (v8)借用トークン (v9)
参照の返却❌ ハードコードで禁止✅ トークンは戻り値とともに伝播
構造体への格納❌ ハードコードで禁止✅ トークンは構造体フィールドとして保持可能
Lambda の明示的引数❌ ハードコードで禁止✅ Lambda は明示的引数を使用
特別ルール3 つ(引数のみ/返却不可/格納不可)0 つ——型属性から自然に推論
借用検査専用の交差借用検査型検査器のフロー依存活性分析
ライフタイムアノテーション不要不必要
ランタイムオーバーヘッドゼロゼロ(ゼロサイズ型、コンパイル後消滅)
エラーメッセージ「借用はエスケープできない」「WriteToken(#3) は既に移動済み」(通常の型エラー)
ユーザのメンタルモデル「借用」の特別な位置づけを理解する&T は複製可能、&mut T は複製不可

3. ref キーワード(コンパイラの自動最適化) ​

ref はスコープを跨いだ共有の唯一の方法。内部実装が Rc か Arc かは、ユーザが気にする必要はない。

3.1 基本使用方法 ​

yaoxiang
p: Point = Point(1.0, 2.0)
shared = ref p                   # 共有、コンパイラが実装を自動選択

# タスクを跨いだ共有
@block
main: () -> Void = {
    data = ref heavy_data
    spawn { use(data) }           # コンパイラ:タスクを跨ぐ → Arc
    spawn { use(data) }           # コンパイラ:タスクを跨ぐ → Arc
}

# 単一タスク内での共有
@block
main: () -> Void = {
    data = ref heavy_data
    use(data)                     # コンパイラ:タスクを跨がない → Rc
}

ユーザのメンタルモデル:ref = 共有保持。それだけで十分。

3.2 コンパイラのエスケープ解析:Rc vs Arc ​

ref のデータフロー解析:

他タスクへエスケープしない → Rc(非アトミック参照カウント、低オーバーヘッド)
他タスクへエスケープする   → Arc(アトミック参照カウント、スレッドセーフ)

3.3 循環検出戦略 ​

タスク内循環 → 静かに許可。
  ├── 各タスクには明確なライフタイム境界がある——タスク終了時にすべてのリソース(ref 循環を含む)が統一解放される。
  ├── 長期実行サービスはリクエスト/接続ごとに子タスクを作成すべき——子タスク終了で自動回収され、累積リークしない。
  ├── ref は常に生存し続け、セマンティクスは混じり気なし。
  └── ユーザはタスク内で双方向強参照を構築する権限を持つ(例:グラフ計算の中間状態)。

タスク跨ぎ循環 → lint(デフォルトは warn、設定可能)。
  ├── プログラムの挙動は正しく、実際にリークはしない(親タスク終了時に子タスクのリソースが全解放される)。
  ├── しかしタスク跨ぎ強参照は所有権境界が曖昧であることを意味し、再考する価値がある。
  ├── デフォルトは warn レベル、コンパイルは成功するがヒントが表示される。
  └── チーム設定で deny にでき、CI 品質ゲートに組み込める。

Lint レベル(Rust の clippy に類似):

レベル挙動シナリオ
allowチェックしない個人プロジェクト
warn(デフォルト)コンパイル成功、ヒント表示開発段階
denyコンパイル失敗チーム CI 品質ゲート
forbidコンパイル失敗、上書き不可組織レベルの強制ルール
yaoxiang
# タスク内循環:静かに許可、双方向強参照
build_graph: () -> Void = {
    a = Node("a")
    b = Node("b")
    a.next = ref b
    b.prev = ref a                # 循環。タスク終了時に統一解放。
}

# タスク跨ぎ循環:lint(デフォルト warn)
@block
parent_task: () -> Void = {
    shared_a = ref a
    shared_b = ref b
    spawn {
        shared_a.child = ref shared_b   # ⚠️ warn: タスク跨ぎ循環参照
    }
}

プロジェクト設定例:

toml
# yaoxiang.toml
[lints]
cross-task-cycle = "deny"    # タスク跨ぎ循環は CI で即座に拒否
循環タイプ挙動理由
タスク内 ref 循環チェックしないユーザの権限、タスク終了時に統一解放
タスク跨ぎ ref 循環lint(デフォルト warn)再考を促す、deny に設定可能

3.4 Weak:標準ライブラリで提供 ​

yaoxiang
use std.weak

# 上級ユーザの明示的選択
a.next = ref b
b.prev = std.weak.new(a.next)   # ユーザがどの方向を弱くするかを明示的に制御

Weak は言語組み込みではなく、標準ライブラリの型である。 日常的には ref で十分。メモリを細かく制御したい上級ユーザは手動で Weak を導入する。

2026-08-03 改訂:独立した std.weak モジュールとして実装(std.rc は存在しない——ref は言語キーワードでありモジュールではない;モジュールパスは std.weak に統一され、構築/昇格エントリは std.weak.new / std.weak.upgrade)。初稿で想定していた std.rc.Weak は採用されず、本改訂が優先される。

3.5 借用トークン vs ref ​

&T / &mut Tref
何をするちら見/その場変更共有保持
範囲トークン値のスコープに従うスコープを跨ぐ
コストゼロオーバーヘッド(ゼロサイズ型)Rc または Arc(コンパイラが選択)
エスケープ可(トークンは戻り値/構造体/クロージャで伝播)本来エスケープ用途
タスク跨ぎ不可(トークンはコンパイル時権限証明であり、タスクを跨いで渡せない)可(コンパイラが Arc を自動選択)
循環関与しないタスク内は静かに許可、タスク跨ぎは lint

4. clone() —— 明示的コピー ​

yaoxiang
p: Point = Point(1.0, 2.0)
p2 = p.clone()                   # ディープコピー
# p と p2 は独立しており、相互に影響しない

いつ使うか:元の値を保持しつつ、Move にも共有にも適さないシナリオ。

5. unsafe + 生ポインタ(システムレベルプログラミング) ​

yaoxiang
p: Point = Point(1.0, 2.0)

unsafe {
    ptr: *Point = &p              # 生ポインタ
    (*ptr).x = 0.0                # デリファレンス(ユーザが安全性を保証)
    ptr2 = ptr + 1                # ポインタ演算
}

制限:

  • unsafe ブロック内でのみ使用可能
  • ユーザはダングリング、解放後使用がないことを保証
  • FFI、メモリ操作などのシステムレベルプログラミングに使用

6. 所有権グラデーション概要 ​

  借用トークン(ゼロオーバーヘッド)     Move(ゼロオーバーヘッド)      共有(必要に応じたコスト)    コピー
   │                      │                  │                │
  &T 複製可能トークン        デフォルトの所有権移転     ref Rc/Arc       clone()
  &mut T 線形トークン      チェイン消費バックフロー       コンパイラが自動選択      明示的ディープコピー
   │                      │                  │                │
  トークン値のスコープ内           スコープ内           スコープを跨ぐ         いつでも
  返却可/構造体に格納可         T -> T バックフロー         ref タスク跨ぎ → Arc  独立コピー
  ゼロサイズ、コンパイル後消滅        T -> Void 消費         ref タスク跨ぎなし → Rc
  ゼロサイズ、コンパイル後消滅                          タスク内循環は静かに許可
                                            タスク跨ぎ循環は lint
                                            標準ライブラリ Weak でエスケープ

総合例 ​

yaoxiang
Point: Type = {
    x: Float,
    y: Float,

    # &T:読み取り専用トークン
    print: (self: &Point) -> Void = {
        print(self.x)
        print(self.y)
    }

    # &mut T:可変トークン
    shift: (self: &mut Point, dx: Float, dy: Float) -> Void = {
        self.x = self.x + dx
        self.y = self.y + dy
    }

    # Move → Move:消費バックフロー
    scale: (self: Point, f: Float) -> Point = {
        self.x = self.x * f
        self.y = self.y * f
        self                            # 奪い、変更し、返す
    }

    # 参照を返す:トークンは戻り値で伝播
    get_x: (self: &Point) -> (&Float, &Point) = {
        return (&self.x, self)
    }
}

# Lambda の明示的引数
double: (x: Int) -> Int = (x) => x * 2

# 総合使用
p = Point(1.0, 2.0)
p.print()                           # &Point トークン
p.shift(1.0, 1.0)                   # &mut Point トークン
p = p.scale(2.0)                    # Move → バックフロー
shared = ref p                      # ref 共有
spawn { use(shared) }

# 独立したクローン
backup = p.clone()

# タスク内循環:静かに許可
a = Node("a")
b = Node("b")
a.next = ref b
b.prev = ref a                      # 循環、タスク終了時に統一解放

# unsafe システムレベル
unsafe {
    ptr: *Point = &p
    (*ptr).x = 0.0
}

型システム制約 ​

Dup 型属性 ​

Dup(Duplicable)はコンパイラが自動管理する型属性であり、シャローコピーを意味する:代入/引数渡し時にはハンドル/トークンがコピーされ、基底データが共有される。これは Move(所有権移転)や Clone(明示的ディープコピー、独立コピー作成)と 3 段階グラデーションを成す。

Dup と Clone は直交する概念——Dup はハンドルをコピーしてデータを共有し、Clone は独立したコピーを作成する。1 つの型が Dup と Clone の両方をサポートすることも、いずれか一方のみをサポートすることも可能。

型DupClone説明
&T✅(トークン複製、複数ビューが同じデータを指す)✅読み取り専用トークン
ref T✅(参照カウント+1、ヒープデータを共有)✅共有保持(コンパイラが Rc/Arc を自動選択)
String, Bytes✅(内部参照カウント、ハンドル共有で基底バッファを共有)✅文字列/バイト
&mut T❌(線形、排他的)❌可変トークン
*T❌❌生ポインタ
struct派生(全フィールドが Dup の場合に自動派生)✅構造体

プリミティブ値型(Int, Float, Bool, Char)の代入挙動はコンパイラ組み込みの値コピーである——2 つの値は完全に独立しており、シャローコピーではない。これらは Dup 型属性には属さず、コンパイラのネイティブ処理である。


性能分析 ​

操作コスト説明
Moveゼロポインタ移動
&T / &mut Tゼロゼロサイズ型、コンパイル後消滅、ランタイムオーバーヘッドゼロ
ref(タスク跨ぎなし)低Rc にコンパイル、非アトミック操作
ref(タスク跨ぎ)中Arc にコンパイル、アトミック操作
clone()型による小オブジェクトは高速、大オブジェクトは低速
unsafe + *Tゼロ直接メモリ操作

比較 ​

言語共有メカニズムメモリ管理循環処理複雑度
RustArc / Mutex + 借用検査コンパイル時検査手動 Weak高
Gochan / pointerGCGC低
C++shared_ptrRAIIweak_ptr中
YaoXiangref + 借用トークンRAIIタスク境界解放 / タスク跨ぎ lint / 標準ライブラリ Weak低

トレードオフ ​

利点 ​

  1. 統一:&T/&mut T は通常の型であり、特別な言語機能ではない。RFC-010 の name: type = value と完全に一貫
  2. シンプル:ライフタイムなし、借用検査は型システム命題に次元削減。&T は複製可能、&mut T は複製不可——2 つの型属性
  3. 強力:参照返却、構造体格納、クロージャキャプチャが可能——表現力は Rust と同等
  4. コンパイラのスマート化:ref が Rc/Arc を自動選択、呼び出し側が借用を自動選択
  5. 決定論的:ref は確実に生存させ続け、密かに弱参照には変わらない
  6. 高性能:Move はゼロコピー、トークンはゼロオーバーヘッド(ゼロサイズ型、コンパイル後消滅)
  7. 柔軟:unsafe + *T でシステムレベルプログラミングをサポート

欠点 ​

  1. ジェネリックブランドパラメータの伝染:トークンがブランド識別子を運ぶため、参照を返す関数のシグネチャに追加のジェネリックパラメータが現れる
  2. ref のランタイムオーバーヘッド:アトミック操作にコストがある(ただしこれは共有の本質的代償)
  3. unsafe のリスク:ユーザが正当性を保証しなければならない
  4. タスク跨ぎ循環は lint でありコンパイルエラーではない:Rust のようにコンパイルエラーとならず、デフォルトが warn であるため、deny をチーム設定して初めて品質ゲートとなる

代替案 ​

案採用しない理由
GCランタイムオーバーヘッドがあり、停止を予測できない
Rust 借用検査器ライフタイム 'a が必要、学習曲線が急峻
純粋な Move並列共有を処理できない
生ポインタなしシステムレベルプログラミングができない
Rc/Arc をユーザに公開実装詳細をユーザに押し付け、認知負荷が増大する
簡素版借用(v8)エスケープ禁止戦略がクロージャキャプチャや参照返却などの重要な表現力を犠牲にする

設計決定記録 ​

決定決定理由日付
デフォルト値Move(ゼロコピー)高性能、ゼロオーバーヘッド2025-01-15
共有メカニズムref キーワード、コンパイラの自動最適化ユーザはシンプル、コンパイラが担当2025-01-15
借用&T/&mut T をゼロサイズトークン型に型属性(Dup/Linear)から自然に権限を推論、型システムを統一2025-01-15
借用トークン簡素版借用を置き換え、&T Dup、&mut T Linear「エスケープ禁止」などの特別ルールを除去、クロージャキャプチャ/参照返却/構造体格納をサポート2026-05-29
コピーclone()明示的なセマンティクス2025-01-15
システムレベル*T + unsafeシステムプログラミングをサポート2025-01-15
ライフタイム実装しないトークンは値であり、ライフタイムは Move/RAII で統一管理、借用を所有権問題に次元削減2025-01-15
Rc/Arcコンパイラが自動選択し、ユーザには不可視認知負荷を低減2025-01-15
循環参照タスク内はチェックなし、タスク跨ぎは lint(デフォルト warn)構造化並行の自然な保証、lint は deny に設定可能2025-01-16
Weak標準ライブラリで提供上級ユーザの明示的選択2025-01-16
消費分析削除ミニ借用検査器は不要2026-05-11
所有権バックフロー削除(T) -> T シグネチャ自体がドキュメント2026-05-11
空状態の再利用削除(機能として)Move 後の再代入は自然な挙動2026-05-11
逆関数/部分消費/フィールド三層可変性削除過剰設計2026-05-11
Lambda の非暗黙的キャプチャLambda は明示的引数のみを使用し、外側変数を暗黙的にキャプチャしない;コンテキストはカリー化により作成時点で固定(SPEC §12.3)クロージャ定義箇所のスコープは既に死んでいる可能性がある;作成時点(呼び出しスコープ生存中)での値固定は安全2026-06-16

バージョン履歴 ​

バージョン主要変更日付
v1初稿:Rust 所有権モデルに基づく2025-01-08
v8過剰設計を削除(逆関数/部分消費/フィールド三層可変性/消費分析/所有権バックフロー/空状態再利用)、簡素版借用 &T/&mut T を追加2026-05-11
v9借用トークンシステムが簡素版借用を置き換え、型システムを統一;トークン衝突検出をホーア命題に修正、RFC-009a 参照2026-06-13

未決議議題 ​

議題説明状態
Drop 構文明示的な drop() 関数の必要性議論待ち
エスケープ解析アルゴリズムref のタスク跨ぎ検出実装議論待ち
トークン衝突検出ホーア論理命題、後述参照✅ 解決済み(詳細は RFC-009a 参照)

トークン衝突検出:ホーア論理命題 ​

トークン衝突検出の完全な解決策は RFC-009a: トークンライフタイム解析——ホーア証明パイプラインに基づく を参照。核心ポイント:

トークン活性はホーア論理命題である。{衝突する ReadToken がすべて死亡} write(data) {WriteToken は安全に取得可能}——型検査やユーザ述語検証と RFC-027 の証明パイプラインを共有する。コンパイラが借用命題(borrow_conflict、use_after_move、use_after_drop、mut_violation)を自動生成し、パイプラインが Proved / Disproved / Unproven を返す。

借用検査は消えていない——次元削減された。 BorrowChecker は BorrowPredicateEmitter となり、検査ではなく命題を生成する。これは「型検査器」の概念と完全に並行する:型検査器は型等価命題を生成し、借用命題生成器は借用命題を生成し、同一のパイプラインが検証する。

ブランド ID(#42)は 'a である。 情報は完全に同一で、エンコーディングが異なる。'a は型シグネチャで見えるが、#42 はコンパイラ内部にある。新しい解析を発明したのではなく、ライフタイムを型層から証明層に降ろしたのである。

アルゴリズム概要(詳細は RFC-009a 参照):

  • ブランドツリーのプレフィックスマッチング → 衝突トークンの特定(O(depth)、深さ ≤ 3)
  • 後向き BFS → コンシューマから出発、break で逆辺を切断、構造解析で 95%+ のシナリオをカバー(高速パス)
  • SMT ロジック切断 → while + パス条件時のみ呼び出し(低速パス、極めて稀)

参考文献 ​

YaoXiang 公式ドキュメント ​

外部参考 ​


ライフサイクルと帰属 ​

状態場所説明
ドラフトdocs/design/rfc/著者の草稿、提出審査待ち
審査中docs/design/rfc/オープンなコミュニティ議論とフィードバック
受理済みdocs/design/accepted/正式な設計ドキュメントになる
却下済みdocs/design/rfc/RFC ディレクトリに保持