RFC-011a: インターフェース実装と動的ディスパッチ
親 RFC: RFC-011: ジェネリクス型システム設計
本 RFC は RFC-011 §2.1-2.4 のインターフェース制約部分を補足し、置換するものです。
摘要
RFC-011 はジェネリクス型システムを定義しましたが、インターフェース実装メカニズムの詳細な説明がありません。本ドキュメントでは以下を補足します:
- インターフェース宣言:型定義内で直接インターフェース名を記述し、
implキーワードは不要 - メソッド実装:内部宣言と外部宣言の両方をサポート
- オーバーロード規則:シグネチャ的不同はオーバーロード可、同シグネチャはエラー(オーバーライド禁止)
- デフォルト値:フィールドの直後に
= valueを記述 - 動的ディスパッチ:コンパイル時型収集 + インターフェース照合、仮想テーブルなし
コア設計:
# インターフェース定義
Animal: Type = {
speak: (Self) -> String,
}
# 型定義(内部宣言)
Dog: Type = {
x: Int = 10,
Animal, # インターフェース宣言
speak: (Self) -> String = "Woof",
}
# 外部宣言(オーバーロード)
Dog.speak: (Self, volume: Int) -> String = "WOOF"
# 異種コンテナ(動的ディスパッチ)
animals: List(Animal) = [Dog.new(), Cat.new()]
animals[0].speak() # "Woof"排除した複雑さ:
- ❌
implキーワードなし - ❌
dyn Trait + 'aアノテーションなし - ❌ 仮想テーブルなし(コンパイル時型収集 + 列挙型ラッパー)
- ❌ オーバーライドなし(オーバーロード規則を統一)
動機
RFC-011 の不足
RFC-011 はジェネリクス型システムを定義しましたが、以下の詳細がありません:
| 問題 | 説明 |
|---|---|
| インターフェース宣言構文 | 型がインターフェースを実装していることを宣言する方法 |
| メソッド実装位置 | 内部宣言か外部宣言か? |
| オーバーロード規則 | 同名メソッドの処理方法? |
| デフォルト値構文 | フィールドのデフォルト値設定方法? |
| 動的ディスパッチ | 異種コンテナの實現方法? |
設計目標
- 簡潔:
implキーワードが不要 - 柔軟:メソッド実装は内部・外部どちらもサポート
- 統一:オーバーロード規則を一貫させる
- 便利:デフォルト値構文がシンプル
- ゼロオーバーヘッド:仮想テーブルなし、コンパイル時型収集
Rust との比較
| 特性 | Rust | YaoXiang |
|---|---|---|
| インターフェース宣言 | impl Animal for Dog { ... } | Dog: Type = { Animal, ... } |
| メソッド実装 | impl ブロック内で | 内部または外部 |
| オーバーロード | サポート外 | サポート(シグネチャ不同) |
| デフォルト値 | #[default] が必要 | 直接 = value を記述 |
| 異種コンテナ | Vec<Box<dyn Animal + 'a>> | List(Animal) |
| 動的ディスパッチ | 仮想テーブル参照 | コンパイル時型収集 |
提案
1. インターフェース宣言
コア規則:型定義内で直接インターフェース名を記述し、impl キーワードは不要。
# インターフェース定義
Animal: Type = {
speak: (Self) -> String,
}
# 型宣言がインターフェースを実装
Dog: Type = {
x: Int,
Animal, # インターフェース宣言
}コンパイラ処理:
Animalがインターフェース型であることを認識DogがAnimalが要求する全メソッドを持つか検査- 通過 → 実装証明を生成
- 失敗 → コンパイルエラー
糖衣構文の同値変換:
Dog: Type = {
x: Int,
Animal, # Animal のメソッドを展開した同値だが、ソースマーキングを保持
}
# 同値(ただしソース情報を保持)
Dog: Type = {
x: Int,
speak: (Self) -> String, # Animal から
}ソースマーキングが必要な理由:
- 直接展開ではソース情報が失われる
- ソースマーキングは実装証明の生成に使用
- 実行時に証明を通じて正しいメソッドを検索
2. メソッド実装
コア規則:メソッド実装は内部宣言と外部宣言の両方をサポート。
2.1 内部宣言
Dog: Type = {
x: Int = 10,
Animal,
speak: (Self) -> String = "Woof", # メソッド実装は内部
}2.2 外部宣言
Dog: Type = {
x: Int,
Animal,
}
# メソッド実装は外部
Dog.speak: (Self) -> String = "Woof"2.3 混合宣言
Dog: Type = {
x: Int = 10,
Animal,
speak: (Self) -> String = "Woof", # 一部のメソッドは内部
}
# 一部のメソッドは外部
Dog.play: (Self) -> Void = { ... }コンパイラ処理:
- 全定義を収集(内部と外部)
- シグネチャでグループ化(オーバーロード)
- オーバーライドがあるか検査(エラー)
- インターフェース完全性を検査
- 実装証明を生成
3. オーバーロードとオーバーライド
コア規則:
- シグネチャ不同 → オーバーロード → 許可
- 同シグネチャ → オーバーライド → エラー
3.1 オーバーロード(許可)
# パラメータ型が異なるためオーバーロード許可
Dog.speak: (Self) -> String = "Woof"
Dog.speak: (Self, volume: Int) -> String = "WOOF"3.2 オーバーライド(禁止)
# シグネチャが完全に同一のためオーバーライド禁止
Dog.speak: (Self) -> String = "Woof"
Dog.speak: (Self) -> String = "Bark" # ❌ エラー:オーバーライドは不可エラーメッセージ:
エラー:Dog.speak(Self) -> String の重複定義
--> ファイル2:5:1
|
5 | Dog.speak: (Self) -> String = "Bark"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 重複定義
|
--> ファイル1:3:1
|
3 | Dog.speak: (Self) -> String = "Woof"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 最初の定義3.3 規則の統一
内部宣言と外部宣言は同じオーバーロード/オーバーライド規則に従います:
# 内部宣言
Dog: Type = {
x: Int,
Animal,
speak: (Self) -> String = "Woof",
}
# 外部宣言(オーバーロード、許可)
Dog.speak: (Self, volume: Int) -> String = "WOOF"
# 外部宣言(オーバーライド,禁止)
Dog.speak: (Self) -> String = "Bark" # ❌ エラー4. デフォルト値
コア規則:フィールドの直後に = value を記述し、コンストラクタを省略。
Dog: Type = {
x: Int = 10, # デフォルト値
y: Int = 20, # デフォルト値
Animal,
}コンパイラ生成コンストラクタ:
# 全フィールドにデフォルト値あり → パラメータなしコンストラクタを生成
Dog.new: () -> Dog = { x: 10, y: 20 }
# 一部のフィールドにデフォルト値あり → 部分パラメータコンストラクタを生成
Dog.new: (x: Int) -> Dog = { x: x, y: 20 }
Dog.new: (y: Int) -> Dog = { x: 10, y: y }
# 全パラメータコンストラクタ
Dog.new: (x: Int, y: Int) -> Dog = { x: x, y: y }外部宣言デフォルト値:
Dog: Type = {
x: Int,
y: Int,
Animal,
}
# 外部宣言デフォルト値
Dog.x: Int = 10
Dog.y: Int = 20内部宣言と同値。
5. コンパイラ実装
5.1 インターフェースデスクリプタ
// コンパイラ内部:インターフェースデスクリプタ
struct InterfaceDescriptor {
name: String,
methods: Vec<MethodSignature>,
}5.2 型定義
// コンパイラ内部:型定義
struct TypeDefinition {
name: String,
fields: Vec<Field>,
interface_implementations: Vec<InterfaceImplementation>,
}
// インターフェース実装(ソース情報を保持)
struct InterfaceImplementation {
interface: InterfaceId,
methods: HashMap<MethodId, FunctionBody>,
}5.3 実装証明
// コンパイラ内部:実装証明
struct ImplementationProof {
type_id: TypeId,
interface_id: InterfaceId,
methods: Vec<MethodPointer>,
}5.4 コンパイルフロー
1. 型定義を解析し、インターフェース宣言を収集
2. 全メソッド定義を収集(内部と外部)
3. シグネチャでグループ化(オーバーロード)
4. オーバーライドを検査(エラー)
5. インターフェース完全性を検査
6. 実装証明を生成
7. 実行時、値が実装証明を携带6. 動的ディスパッチ
コア設計:コンパイル時型収集 + インターフェース照合、仮想テーブルなし。
6.1 異種コンテナ
# インターフェース定義
Animal: Type = {
speak: (Self) -> String,
}
# 型定義
Dog: Type = {
x: Int,
Animal,
speak: (Self) -> String = "Woof",
}
Cat: Type = {
y: Int,
Animal,
speak: (Self) -> String = "Meow",
}
# 異種コンテナ
animals: List(Animal) = [Dog.new(), Cat.new()]
animals[0].speak() # "Woof"
animals[1].speak() # "Meow"6.2 コンパイル時型収集
コア戦略:所有権追跡によるインクリメンタル構築。 インターフェースを実装する全型をコンパイル時にスキャンするのではなく、各 List(Animal) の所有権操作点でインクリメンタルに収集:
// 構築点
animals: List(Animal) = [Dog.new()] // AnimalGroup = { Dog(Dog) }
// append 点
animals.append(Cat.new()) // コンパイラが append 箇所で Cat を検出 → { Dog, Cat } に拡張
animals.append(Bird.new()) // さらに { Dog, Cat, Bird } に拡張コンパイラ処理(インクリメンタル):
List(I)が初めて構築される箇所に遭遇 → 初期列挙型を生成(現在のコンパイルユニット内で既知の構築型の全て)- 毎回の
append/push/ インデックス代入時 → 値型が既に列挙型にあるか検査;なければ列挙型ヴァリアントを拡張 - 最終列挙型に対してモノ Појединаライズされた
matchディスパッチコードを生成 - コンパイルユニット間:リンク時に各ユニットの列挙型ヴァリアントセットをマージ
自動生成される列挙型:
# コンパイラが自動生成(ユーザには不可視)
AnimalGroup: Type = {
Dog(Dog),
Cat(Cat),
Bird(Bird), # ← append(Bird.new()) がインクリメンタル拡張をトリガ
}
# List(Animal) の内部は List(AnimalGroup) と同等6.3 インターフェース照合検査
重要な洞察:インターフェース照合はコンパイル時に検査され、動的にロードされたプラグインからの型でも問題なし。
# プラグインシステム
plugin = load_plugin("bird.so")
# コンパイラ検査:plugin.create_bird() の返り型が Animal を実装しているか必須
bird: Animal = plugin.create_bird() # コンパイル時検査
# 異種コンテナに投入 —— append 点で列挙型拡張をトリガ
animals: List(Animal) = [Dog.new(), Cat.new()]
animals.append(bird) # コンパイラ:(1) bird が Animal を実装してるか検証 (2) 列挙型を拡張コンパイラ処理:
append引数の返り型を検査- その型が対象インターフェースを実装してるか検証
- 通過 → 列挙型を拡張、投入を許可
- 失敗 → コンパイルエラー
6.4 実行時ディスパッチ
呼び出しフロー(コンパイル時列挙型 match、ImplementationProof は消去済み):
animals[0].speak()
↓
コンパイラ生成の match:
match animals[0] {
AnimalGroup.Dog(d) => d.speak(),
AnimalGroup.Cat(c) => c.speak(),
AnimalGroup.Bird(b) => b.speak(),
}仮想テーブルとの比較:
| 仮想テーブル(Rust) | コンパイル時列挙型(YaoXiang) | |
|---|---|---|
| 検索方式 | 仮想テーブルポインタ → メソッドポインタ | 列挙型 match → 直接呼び出し |
| 実行時オーバーヘッド | 1回の間接アドレッシング | 文字列比較/分岐(CPU 분기予測で最適化可能) |
| コンパイル時生成 | 仮想テーブル | 列挙型 + match |
| ユーザアノテーション | dyn Trait + 'a が必要 | 不要 |
| ImplementationProof | 該当なし | コンパイル時に消去、実行時に存在しない |
YaoXiang の優位性:
- ブランドアノテーション不要
- コンパイル時型安全性
- ユーザ透過的(
dyn Animalを記述する必要がない) - ImplementationProof は純粋なコンパイル時概念で、実行時オーバーヘッドゼロ
6.5 制限と範囲
期内(単一コンパイルユニット): 完全サポート。所有権追跡が全 append/構築点をカバーし、列挙型をインクリメンタルに構築。
コンパイルユニット間: リンク時に各ユニットの列挙型ヴァリアントセットをマージ。設計はリンク時モノminimax:tool_call化と同一メカニズムを共有(各ユニットが部分列挙型を生成、リンカがマージ)。
未サポート: 実行時動的型(完全なダックタイピング)。型セットはコンパイル時に完全に既知である必要があります。
ユースケース分析
基本インターフェース実装
# インターフェース定義
Animal: Type = {
speak: (Self) -> String,
}
# 型定義
Dog: Type = {
x: Int = 10,
Animal,
speak: (Self) -> String = "Woof",
}
# 使用
dog = Dog.new()
dog.speak() # "Woof"複数インターフェース実装
# 複数インターフェース
Animal: Type = {
speak: (Self) -> String,
}
Pet: Type = {
name: (Self) -> String,
}
# 型が複数インターフェースを実装
Dog: Type = {
x: Int = 10,
Animal,
Pet,
speak: (Self) -> String = "Woof",
name: (Self) -> String = "Buddy",
}
# 使用
dog = Dog.new()
dog.speak() # "Woof"
dog.name() # "Buddy"ジェネリクスインターフェース
# ジェネリクスインターフェース
Container: (T: Type) -> Type = {
add: (self: &mut Self, item: T) -> Void,
get: (self: &Self, index: Int) -> T,
}
# ジェネリクスインターフェースを実装
IntList: Type = {
data: Array(Int),
Container(Int),
add: (self: &mut Self, item: Int) -> Void = ...,
get: (self: &Self, index: Int) -> Int = ...,
}異種コンテナ
# インターフェース定義
Animal: Type = {
speak: (Self) -> String,
}
# 型定義
Dog: Type = {
x: Int,
Animal,
speak: (Self) -> String = "Woof",
}
Cat: Type = {
y: Int,
Animal,
speak: (Self) -> String = "Meow",
}
# 異種コンテナ
animals: List(Animal) = [Dog.new(), Cat.new()]
# 使用
for animal in animals {
print(animal.speak())
}
# 出力:
# Woof
# Meowプラグインシステム
# インターフェース定義
Plugin: Type = {
name: (Self) -> String,
execute: (Self) -> Void,
}
# メインプログラム
main: () -> Void = {
# プラグインをロード
plugin1 = load_plugin("plugin1.so")
plugin2 = load_plugin("plugin2.so")
# コンパイラ検査:plugin1 と plugin2 は Plugin インターフェースを実装してる必要がある
plugins: List(Plugin) = [plugin1, plugin2]
# 全プラグインを実行
for plugin in plugins {
print(plugin.name())
plugin.execute()
}
}トレードオフ
利点
- 簡潔:
implキーワードが不要 - 柔軟:メソッド実装は内部・外部どちらもサポート
- 統一:オーバーロード規則が一貫
- 便利:デフォルト値構文がシンプル
- ゼロオーバーヘッド:仮想テーブルなし、コンパイル時型収集
- 型安全:インターフェース照合はコンパイル時に検査
- ユーザ透過:
dyn Animal + 'aを記述する必要がない
欠点
- 制限:実行時動的型をサポート外(完全なダックタイピング)
- コンパイル時オーバーヘッド:各インターフェースに対して列挙型ヴァリアントと match ディスパッチコードを生成する必要がある
- 型セット:コンパイル時に完全に既知である必要がある(単一コンパイルユニット内)
緩和策
- プラグインシステム:コンパイル時インターフェース照合検査を通じてサポート
- 型セット:所有権追跡によるインクリメンタル構築——各
append/構築点で収集し、グローバルスキャンではない - コンパイルユニット間:リンク時に列挙型ヴァリアントセットをマージ、リンク時モノminimax:tool_call化とメカニズムを共有
代替案
| 方案 | 選択しない理由 |
|---|---|
impl キーワード | 構文複雑度が増す |
仮想テーブル(dyn Trait) | ブランドアノテーション('a)が必要 |
| 完全ダックタイピング | 実行時オーバーヘッド、型不安全 |
| 手動列挙型ラッパー | ユーザ負担が大きい |
RFC-009 との関係
ブランドとインターフェース実装:
- インターフェース実装は型レベルにあり、ブランドには関係しない
- ブランドは借用証明レベル(RFC-009a)
- 両者は直交し、互いに影響しない
動的ディスパッチとブランド:
- 動的ディスパッチは実装証明を使用しており、ブランドアノテーションは不要
- 実装証明はコンパイル時に生成され、実行時の検索コストはゼロ
dyn Trait + 'aの複雑さを回避できる
インターフェース継承
インターフェースは別のインターフェースを含めることができます。新しい構文は導入しない——型宣言がインターフェースを使用するのと同じ構文位置で:
Animal: Type = {
speak: (Self) -> String,
}
Pet: Type = {
Animal, # Pet が Animal を継承 — 新しいキーワードなし
name: (Self) -> String,
}
# Dog が Pet を実装する際、Animal と Pet の全メソッドを満たす必要がある
Dog: Type = {
x: Int,
Pet,
speak: (Self) -> String = "Woof", # Animal から
name: (Self) -> String = "Buddy", # Pet から
}設計原則: 継承は存在しますが、乱用は推奨されません。主要な組み合わせ方法は複数のインターフェース宣言です(Dog: Type = { Animal, Pet, ... })。型は直接적으로自身が満たす全インターフェースを宣言でき、継承ツリーを通じて表現する必要はありません。インターフェース継承は明確な「is-a」階層がある場合にのみ使用します。
コンパイラ処理: 継承チェーンを展開します。Pet は { Animal の全メソッド, name: ... } に展開されます。Dog が Pet を宣言すると、コンパイラは Dog が Animal と Pet の全メソッドを同時に満たすことを検証します。
デフォルトメソッド実装
インターフェースはメソッドのデフォルト実装を提供できます。実装型はデフォルト実装をオーバーライドまたは継承することを選択できます:
fmt: Type = {
display: (Self) -> String, # 必須実装
debug: (Self) -> String = Self.display(), # ✅ 同インターフェースメソッドを参照
summary: (Self) -> String = f"<{Self.name}>", # ❌ コンパイルエラー:Self.name は fmt にない
}コア制約:インターフェースは上位実装を仮定してはならない。 デフォルトメソッドは同じインターフェース内で宣言済みのメソッドのみ参照できます。具体的な型のフィールドや他のインターフェースのメソッドはデフォルトメソッドからは可視ではありません——インターフェースは闭合された契約であり、実装型のポケットに手を伸ばすことはできません。この制約違反はインターフェース定義時に直接エラーになります。
継承は下位実装を仮定できる: インターフェース Pet が Animal を継承する際、Pet のデフォルトメソッドは Animal で宣言されたメソッドを使用できます——継承しているので保証があるからです。
Animal: Type = {
speak: (Self) -> String,
}
Pet: Type = {
Animal, # 継承
name: (Self) -> String,
introduce: (Self) -> String = Self.name() + " says " + Self.speak(), # ✅ speak は継承した Animal から
}コンパイル時動作: 型がインターフェースを実装する際、各メソッドに対して:
- 型が提供 → 型のメソッドを使用
- 型が未提供、インターフェースがデフォルトあり → コンパイラがデフォルト実装を型にインライン展開(ゼロ仮想テーブルオーバーヘッド)
- 型が未提供、インターフェースがデフォルトなし → コンパイルエラー
設計原則: デフォルトメソッドは Copy/Clone の自動導出メカニズムに類似——コンパイラが必要時に自動生成し、ユーザがオーバーライド可能。新しい virtual/override/super キーワードを導入しません。
実装フェーズ
| フェーズ | 内容 | 依存 |
|---|---|---|
| Phase 1 | インターフェース宣言構文 | RFC-011 |
| Phase 2 | メソッド実装の内部/外部宣言 | Phase 1 |
| Phase 3 | オーバーロードとオーバーライド規則 | Phase 2 |
| Phase 4 | デフォルト値構文 | Phase 2 |
| Phase 5 | インターフェース継承 | Phase 3 |
| Phase 6 | デフォルトメソッド実装 | Phase 5 |
| Phase 7 | 実装証明生成 | Phase 6 |
| Phase 8 | コンパイル時型収集 | Phase 7 |
| Phase 9 | 動的ディスパッチ実装 | Phase 8 |
設計意思決定記録
| 意思決定 | 決定 | 理由 | 日付 |
|---|---|---|---|
| インターフェース宣言構文 | 型ボディ内で直接インターフェース名を記述 | impl キーワードを排除、インターフェース宣言は型定義の自然な構成部分 | 2026-06-14 |
| 動的ディスパッチ | コンパイル時型収集 + 自動列挙型生成 | 仮想テーブルなし、実行時検索コストゼロ、ユーザ透過的 | 2026-06-14 |
| 外部メソッド宣言 | サポート | 内部宣言と等価な柔軟性、コンパイラがファイル間収集を担当 | 2026-06-14 |
| オーバーライド | 禁止(同シグネチャはエラー) | オーバーライドは予測不能な動作を招く、オーバーロードで全ケースをカバー | 2026-06-14 |
| インターフェース継承 | サポート、新しい構文なし | 型宣言がインターフェースを使うのと同じ構文位置。深い継承ツリーより組合わ推奨 | 2026-07-03 |
| デフォルトメソッド実装 | サポート、Copy/Clone 自動導出に類似 | インターフェースが本体を提供、コンパイラが必要に応じてインライン展開、ユーザがオーバーライド可能 | 2026-07-03 |
| デフォルトメソッド制約 | インターフェース定義時に検証:同インターフェースメソッドのみ参照、上位実装を仮定不可 | インターフェースは闭合された契約。継承は下位実装を仮定できるが、インターフェースは実装型のフィールド/メソッドを仮定できない | 2026-07-03 |
| 型収集戦略 | 所有権追跡によるインクリメンタル構築——各 append/構築点で収集 | 全実装者のグローバルスキャンではなく、所有権操作点で列挙型をインクリメンタル拡張 | 2026-07-03 |
| ImplementationProof | 純粋なコンパイル時概念、実行時に消去 | 実行時は列挙型 match ディスパッチを通じ、証明はコンパイル時検証のみに使用 | 2026-07-03 |
| コンパイルユニット間 | リンク時に各ユニット列挙型ヴァリアントをマージ | リンク時モノminimax:tool_call化とメカニズムを共有、各ユニットが部分列挙型を生成、リンカがマージ | 2026-07-03 |
未解決問題
- [x]
インターフェース継承(インターフェースが他のインターフェースを継承可能)→ サポート、新しい構文なし。Pet: Type = { Animal, ... } - [x]
デフォルトメソッド実装(インターフェースがデフォルト実装を提供可能)→ サポート、Copy 自動導出に類似。インターフェースが本体を提供、コンパイラが按需インライン展開 - [ ] インターフェース制約の高度な用法(関連型、GAT)
- [ ] クロージャとの相互作用(クロージャがインターフェースを実装)
参考文献
- RFC-011: ジェネリクス型システム設計 — 親 RFC
- RFC-009: 所有権モデル設計 — 所有権システム
- RFC-009a: 借用証明パイプライン — ブランドメカニズム
- RFC-010: 統一型構文 — 統一構文
ライフサイクルと行き先
| 状態 | 場所 | 説明 |
|---|---|---|
| レビュー中 | docs/design/rfc/review/ | コミュニティ議論を開放 |
