Skip to content

RFC-011a: インターフェース実装と動的ディスパッチ

親 RFC: RFC-011: ジェネリクス型システム設計

本 RFC は RFC-011 §2.1-2.4 のインターフェース制約部分を補足し、置換するものです。

摘要

RFC-011 はジェネリクス型システムを定義しましたが、インターフェース実装メカニズムの詳細な説明がありません。本ドキュメントでは以下を補足します:

  1. インターフェース宣言:型定義内で直接インターフェース名を記述し、impl キーワードは不要
  2. メソッド実装:内部宣言と外部宣言の両方をサポート
  3. オーバーロード規則:シグネチャ的不同はオーバーロード可、同シグネチャはエラー(オーバーライド禁止)
  4. デフォルト値:フィールドの直後に = value を記述
  5. 動的ディスパッチ:コンパイル時型収集 + インターフェース照合、仮想テーブルなし

コア設計

yaoxiang
# インターフェース定義
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 はジェネリクス型システムを定義しましたが、以下の詳細がありません:

問題説明
インターフェース宣言構文型がインターフェースを実装していることを宣言する方法
メソッド実装位置内部宣言か外部宣言か?
オーバーロード規則同名メソッドの処理方法?
デフォルト値構文フィールドのデフォルト値設定方法?
動的ディスパッチ異種コンテナの實現方法?

設計目標

  1. 簡潔impl キーワードが不要
  2. 柔軟:メソッド実装は内部・外部どちらもサポート
  3. 統一:オーバーロード規則を一貫させる
  4. 便利:デフォルト値構文がシンプル
  5. ゼロオーバーヘッド:仮想テーブルなし、コンパイル時型収集

Rust との比較

特性RustYaoXiang
インターフェース宣言impl Animal for Dog { ... }Dog: Type = { Animal, ... }
メソッド実装impl ブロック内で内部または外部
オーバーロードサポート外サポート(シグネチャ不同)
デフォルト値#[default] が必要直接 = value を記述
異種コンテナVec<Box<dyn Animal + 'a>>List(Animal)
動的ディスパッチ仮想テーブル参照コンパイル時型収集

提案

1. インターフェース宣言

コア規則:型定義内で直接インターフェース名を記述し、impl キーワードは不要。

yaoxiang
# インターフェース定義
Animal: Type = {
    speak: (Self) -> String,
}

# 型宣言がインターフェースを実装
Dog: Type = {
    x: Int,
    Animal,  # インターフェース宣言
}

コンパイラ処理

  1. Animal がインターフェース型であることを認識
  2. DogAnimal が要求する全メソッドを持つか検査
  3. 通過 → 実装証明を生成
  4. 失敗 → コンパイルエラー

糖衣構文の同値変換

yaoxiang
Dog: Type = {
    x: Int,
    Animal,  # Animal のメソッドを展開した同値だが、ソースマーキングを保持
}

# 同値(ただしソース情報を保持)
Dog: Type = {
    x: Int,
    speak: (Self) -> String,  # Animal から
}

ソースマーキングが必要な理由

  • 直接展開ではソース情報が失われる
  • ソースマーキングは実装証明の生成に使用
  • 実行時に証明を通じて正しいメソッドを検索

2. メソッド実装

コア規則:メソッド実装は内部宣言と外部宣言の両方をサポート。

2.1 内部宣言

yaoxiang
Dog: Type = {
    x: Int = 10,
    Animal,
    speak: (Self) -> String = "Woof",  # メソッド実装は内部
}

2.2 外部宣言

yaoxiang
Dog: Type = {
    x: Int,
    Animal,
}

# メソッド実装は外部
Dog.speak: (Self) -> String = "Woof"

2.3 混合宣言

yaoxiang
Dog: Type = {
    x: Int = 10,
    Animal,
    speak: (Self) -> String = "Woof",  # 一部のメソッドは内部
}

# 一部のメソッドは外部
Dog.play: (Self) -> Void = { ... }

コンパイラ処理

  1. 全定義を収集(内部と外部)
  2. シグネチャでグループ化(オーバーロード)
  3. オーバーライドがあるか検査(エラー)
  4. インターフェース完全性を検査
  5. 実装証明を生成

3. オーバーロードとオーバーライド

コア規則

  • シグネチャ不同 → オーバーロード → 許可
  • 同シグネチャ → オーバーライド → エラー

3.1 オーバーロード(許可)

yaoxiang
# パラメータ型が異なるためオーバーロード許可
Dog.speak: (Self) -> String = "Woof"
Dog.speak: (Self, volume: Int) -> String = "WOOF"

3.2 オーバーライド(禁止)

yaoxiang
# シグネチャが完全に同一のためオーバーライド禁止
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 規則の統一

内部宣言と外部宣言は同じオーバーロード/オーバーライド規則に従います

yaoxiang
# 内部宣言
Dog: Type = {
    x: Int,
    Animal,
    speak: (Self) -> String = "Woof",
}

# 外部宣言(オーバーロード、許可)
Dog.speak: (Self, volume: Int) -> String = "WOOF"

# 外部宣言(オーバーライド,禁止)
Dog.speak: (Self) -> String = "Bark"  # ❌ エラー

4. デフォルト値

コア規則:フィールドの直後に = value を記述し、コンストラクタを省略。

yaoxiang
Dog: Type = {
    x: Int = 10,  # デフォルト値
    y: Int = 20,  # デフォルト値
    Animal,
}

コンパイラ生成コンストラクタ

yaoxiang
# 全フィールドにデフォルト値あり → パラメータなしコンストラクタを生成
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 }

外部宣言デフォルト値

yaoxiang
Dog: Type = {
    x: Int,
    y: Int,
    Animal,
}

# 外部宣言デフォルト値
Dog.x: Int = 10
Dog.y: Int = 20

内部宣言と同値

5. コンパイラ実装

5.1 インターフェースデスクリプタ

rust
// コンパイラ内部:インターフェースデスクリプタ
struct InterfaceDescriptor {
    name: String,
    methods: Vec<MethodSignature>,
}

5.2 型定義

rust
// コンパイラ内部:型定義
struct TypeDefinition {
    name: String,
    fields: Vec<Field>,
    interface_implementations: Vec<InterfaceImplementation>,
}

// インターフェース実装(ソース情報を保持)
struct InterfaceImplementation {
    interface: InterfaceId,
    methods: HashMap<MethodId, FunctionBody>,
}

5.3 実装証明

rust
// コンパイラ内部:実装証明
struct ImplementationProof {
    type_id: TypeId,
    interface_id: InterfaceId,
    methods: Vec<MethodPointer>,
}

5.4 コンパイルフロー

1. 型定義を解析し、インターフェース宣言を収集
2. 全メソッド定義を収集(内部と外部)
3. シグネチャでグループ化(オーバーロード)
4. オーバーライドを検査(エラー)
5. インターフェース完全性を検査
6. 実装証明を生成
7. 実行時、値が実装証明を携带

6. 動的ディスパッチ

コア設計:コンパイル時型収集 + インターフェース照合、仮想テーブルなし。

6.1 異種コンテナ

yaoxiang
# インターフェース定義
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)所有権操作点でインクリメンタルに収集:

yaoxiang
// 構築点
animals: List(Animal) = [Dog.new()]       // AnimalGroup = { Dog(Dog) }

// append 点
animals.append(Cat.new())                  // コンパイラが append 箇所で Cat を検出 → { Dog, Cat } に拡張
animals.append(Bird.new())                 // さらに { Dog, Cat, Bird } に拡張

コンパイラ処理(インクリメンタル):

  1. List(I) が初めて構築される箇所に遭遇 → 初期列挙型を生成(現在のコンパイルユニット内で既知の構築型の全て)
  2. 毎回の append / push / インデックス代入時 → 値型が既に列挙型にあるか検査;なければ列挙型ヴァリアントを拡張
  3. 最終列挙型に対してモノ Појединаライズされた match ディスパッチコードを生成
  4. コンパイルユニット間:リンク時に各ユニットの列挙型ヴァリアントセットをマージ

自動生成される列挙型

yaoxiang
# コンパイラが自動生成(ユーザには不可視)
AnimalGroup: Type = {
    Dog(Dog),
    Cat(Cat),
    Bird(Bird),    # ← append(Bird.new()) がインクリメンタル拡張をトリガ
}

# List(Animal) の内部は List(AnimalGroup) と同等

6.3 インターフェース照合検査

重要な洞察:インターフェース照合はコンパイル時に検査され、動的にロードされたプラグインからの型でも問題なし。

yaoxiang
# プラグインシステム
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) 列挙型を拡張

コンパイラ処理

  1. append 引数の返り型を検査
  2. その型が対象インターフェースを実装してるか検証
  3. 通過 → 列挙型を拡張、投入を許可
  4. 失敗 → コンパイルエラー

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化と同一メカニズムを共有(各ユニットが部分列挙型を生成、リンカがマージ)。

未サポート: 実行時動的型(完全なダックタイピング)。型セットはコンパイル時に完全に既知である必要があります。

ユースケース分析

基本インターフェース実装

yaoxiang
# インターフェース定義
Animal: Type = {
    speak: (Self) -> String,
}

# 型定義
Dog: Type = {
    x: Int = 10,
    Animal,
    speak: (Self) -> String = "Woof",
}

# 使用
dog = Dog.new()
dog.speak()  # "Woof"

複数インターフェース実装

yaoxiang
# 複数インターフェース
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"

ジェネリクスインターフェース

yaoxiang
# ジェネリクスインターフェース
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 = ...,
}

異種コンテナ

yaoxiang
# インターフェース定義
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

プラグインシステム

yaoxiang
# インターフェース定義
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()
    }
}

トレードオフ

利点

  1. 簡潔impl キーワードが不要
  2. 柔軟:メソッド実装は内部・外部どちらもサポート
  3. 統一:オーバーロード規則が一貫
  4. 便利:デフォルト値構文がシンプル
  5. ゼロオーバーヘッド:仮想テーブルなし、コンパイル時型収集
  6. 型安全:インターフェース照合はコンパイル時に検査
  7. ユーザ透過dyn Animal + 'a を記述する必要がない

欠点

  1. 制限:実行時動的型をサポート外(完全なダックタイピング)
  2. コンパイル時オーバーヘッド:各インターフェースに対して列挙型ヴァリアントと match ディスパッチコードを生成する必要がある
  3. 型セット:コンパイル時に完全に既知である必要がある(単一コンパイルユニット内)

緩和策

  1. プラグインシステム:コンパイル時インターフェース照合検査を通じてサポート
  2. 型セット:所有権追跡によるインクリメンタル構築——各 append/構築点で収集し、グローバルスキャンではない
  3. コンパイルユニット間:リンク時に列挙型ヴァリアントセットをマージ、リンク時モノminimax:tool_call化とメカニズムを共有

代替案

方案選択しない理由
impl キーワード構文複雑度が増す
仮想テーブル(dyn Traitブランドアノテーション('a)が必要
完全ダックタイピング実行時オーバーヘッド、型不安全
手動列挙型ラッパーユーザ負担が大きい

RFC-009 との関係

ブランドとインターフェース実装

  • インターフェース実装は型レベルにあり、ブランドには関係しない
  • ブランドは借用証明レベル(RFC-009a)
  • 両者は直交し、互いに影響しない

動的ディスパッチとブランド

  • 動的ディスパッチは実装証明を使用しており、ブランドアノテーションは不要
  • 実装証明はコンパイル時に生成され、実行時の検索コストはゼロ
  • dyn Trait + 'a の複雑さを回避できる

インターフェース継承

インターフェースは別のインターフェースを含めることができます。新しい構文は導入しない——型宣言がインターフェースを使用するのと同じ構文位置で:

yaoxiang
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: ... } に展開されます。DogPet を宣言すると、コンパイラは DogAnimalPet の全メソッドを同時に満たすことを検証します。

デフォルトメソッド実装

インターフェースはメソッドのデフォルト実装を提供できます。実装型はデフォルト実装をオーバーライドまたは継承することを選択できます:

yaoxiang
fmt: Type = {
    display: (Self) -> String,                      # 必須実装
    debug: (Self) -> String = Self.display(),       # ✅ 同インターフェースメソッドを参照
    summary: (Self) -> String = f"<{Self.name}>",   # ❌ コンパイルエラー:Self.name は fmt にない
}

コア制約:インターフェースは上位実装を仮定してはならない。 デフォルトメソッドは同じインターフェース内で宣言済みのメソッドのみ参照できます。具体的な型のフィールドや他のインターフェースのメソッドはデフォルトメソッドからは可視ではありません——インターフェースは闭合された契約であり、実装型のポケットに手を伸ばすことはできません。この制約違反はインターフェース定義時に直接エラーになります。

継承は下位実装を仮定できる: インターフェース PetAnimal を継承する際、Pet のデフォルトメソッドは Animal で宣言されたメソッドを使用できます——継承しているので保証があるからです。

yaoxiang
Animal: Type = {
    speak: (Self) -> String,
}

Pet: Type = {
    Animal,                                              # 継承
    name: (Self) -> String,
    introduce: (Self) -> String = Self.name() + " says " + Self.speak(),  # ✅ speak は継承した Animal から
}

コンパイル時動作: 型がインターフェースを実装する際、各メソッドに対して:

  1. 型が提供 → 型のメソッドを使用
  2. 型が未提供、インターフェースがデフォルトあり → コンパイラがデフォルト実装を型にインライン展開(ゼロ仮想テーブルオーバーヘッド)
  3. 型が未提供、インターフェースがデフォルトなし → コンパイルエラー

設計原則: デフォルトメソッドは 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)
  • [ ] クロージャとの相互作用(クロージャがインターフェースを実装)

参考文献


ライフサイクルと行き先

状態場所説明
レビュー中docs/design/rfc/review/コミュニティ議論を開放