Skip to content

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

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

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

要約 ​

RFC-011 はジェネリクスシステムを定義したが、インターフェース実装の仕組みを詳しく規定していない。本文書では以下を補足する:

  1. インターフェース宣言:インターフェースはパラメータ化された型——(Self: Type) -> Type、実装時に具体的な型を渡す
  2. メソッド実装:内部宣言と外部宣言の両方をサポート
  3. オーバーロード規則:シグネチャが異なればオーバーロードを許可、同じならエラー(オーバーライド禁止)
  4. デフォルト値:フィールドの直後に直接 = value と書く
  5. 動的ディスパッチ:コンパイル時の型収集 + インターフェースマッチング、仮想テーブルなし

中核設計:

yaoxiang
# インターフェース定義(パラメータ化された型、Self は明示的な型パラメータ)
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

# 型定義(内部宣言)
Dog: Type = {
    x: Int = 10,
    Animal(Dog),  # インターフェースの実体化、Self ↦ Dog
    speak: (self: &Dog) -> String = "Woof",
}

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

# 異種コンテナ(動的ディスパッチ)
animals: List(Animal) = [Dog.new(), Cat.new()]
animals[0].speak()  # "Woof"

レシーバの表記規約(RFC-009 所有権セマンティクスと連携):

  • メソッドレシーバはシグネチャのセマンティクスに従う:&Self = 借用(インターフェースのデフォルト規約——メソッド呼び出しはレシーバを消費しない)、 &mut Self = 可変借用、値渡し Self = レシーバの消費(Move、RFC-009)。
  • impl 側シグネチャの Self は impl 型のエイリアス:インターフェース speak: (self: &Self) はimpl (self: &Dog) / (self: &Self) のいずれにも一致する(Self↦impl 型の置換後完全に一致、§3)。
  • 過去の例の値渡しレシーバ表記((self: Self))は借用を意味していたが、本文書では明示的な &Self に統一移行した。値渡し表記は今後は「消費」セマンティクスのみを表し、両者は混在しない。

除去された複雑さ:

  • ❌ impl キーワード不要
  • ❌ Self マジックキーワード不要(Self は明示的な型パラメータで、T と変わらない)
  • ❌ dyn Trait + 'a 注釈不要
  • ❌ 仮想テーブル不要(コンパイル時の型収集 + enum ラッピング)
  • ❌ オーバーライド不要(オーバーロード規則が統一)

動機 ​

RFC-011 の不備 ​

RFC-011 はジェネリクスシステムを定義したが、以下を詳しく規定していない:

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

設計目標 ​

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

Rust との比較 ​

特性RustYaoXiang
インターフェース宣言impl Animal for Dog { ... }Dog: Type = { Animal(Dog), ... }
メソッド実装impl ブロック内内部または外部
オーバーロード非サポートサポート(シグネチャが異なる場合)
デフォルト値#[default] が必要直接 = value と書く
異種コンテナVec<Box<dyn Animal + 'a>>List(Animal)
動的ディスパッチ仮想テーブルルックアップコンパイル時の型収集
Self キーワードマジックキーワード、暗黙の量化明示的な型パラメータ、T と対等

提案 ​

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

中核ルール:インターフェースはパラメータ化された型 (Self: Type) -> Type であり、Self は明示的な型パラメータで、マジックキーワードではない。実装時はインターフェースを呼び出し、具体的な型を渡す。

yaoxiang
# インターフェース定義(RFC-011 のジェネリック型と完全に一致)
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

# 型宣言でインターフェースを実装
Dog: Type = {
    x: Int,
    Animal(Dog),  # インターフェースの実体化、Self ↦ Dog
}

コンパイラの処理:

  1. Animal(Dog) が (Self: Type) -> Type の実体化呼び出しであることを識別
  2. Self ↦ Dog の置換を実行:Animal(Dog) を展開 → { speak: (self: &Dog) -> String }
  3. Dog が必要なメソッドをすべて提供しているかチェック(シグネチャの一致)
  4. 通過 → 実装証明を生成
  5. 失敗 → コンパイルエラー

展開の等価性:

yaoxiang
Dog: Type = {
    x: Int,
    Animal(Dog),  # Animal のメソッドを展開、由来の印を保持
}

# 等価(由来情報を保持)
Dog: Type = {
    x: Int,
    speak: (self: &Dog) -> String,  # Animal 由来、Self は Dog に置換済み
}

由来の印が必要な理由:

  • 直接展開では由来情報が失われる
  • 由来の印は実装証明の生成に使われる
  • ランタイムは証明を通じて正しいメソッドを見つける

1.1 Self 型パラメータと型検査のタイミング ​

Self はインターフェースの明示的な型パラメータであり、マジックキーワードではない。Animal: (Self: Type) -> Type と List: (T: Type) -> Type は同じもの——(Type) -> Type 型コンストラクタ。

型検査のタイミング:

  • インターフェース定義時:{ speak: (self: &Self) -> String } の Self は抽象的な型パラメータで、構文チェックのみ行う。
  • 実体化点:Animal(Dog) の時点で Self ↦ Dog を実行し、展開後に完全な型検査(シグネチャ一致、メソッド存在性)を行う。

これにより、RFC-011 で問題となっていた Self の暗黙のマジックキーワード化を回避できる。Self は型定義には現れず、インターフェースのパラメータリストに一度だけ現れ、T と完全に等価である。

1.2 フィールド名とメソッド名の名前空間 ​

型のフィールド名とメソッド名は同じ名前空間を共有する。インターフェースを展開した後、インターフェースメソッド名と型のフィールド名が衝突した場合、コンパイルエラーとする:

yaoxiang
Drawable: (Self: Type) -> Type = {
    x: (self: &Self) -> Int,    # メソッド名が x
}

Point: Type = {
    x: Int,                     # フィールドも x
    Drawable(Point),            # ❌ コンパイルエラー:Drawable はメソッド x を要求するが、フィールド x と衝突
}

フィールドアクセス point.x とメソッド呼び出し point.x() は構文上区別できない。名前空間を統一することで曖昧さを排除する。

2. メソッド実装 ​

中核ルール:メソッド実装は内部宣言と外部宣言の両方をサポートする。

2.1 内部宣言 ​

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

2.2 外部宣言 ​

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

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

2.3 混在宣言 ​

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

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

コンパイラの処理:

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

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

中核ルール:

  • シグネチャが異なる → オーバーロード → 許可
  • シグネチャが同じ → オーバーライド → エラー

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

yaoxiang
# 引数型が異なる場合はオーバーロードを許可
Dog.speak: (self: &Dog) -> String = "Woof"
Dog.speak: (self: &Dog, volume: Int) -> String = "WOOF"

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

yaoxiang
# シグネチャが完全に同じ場合はオーバーライドを禁止
Dog.speak: (self: &Dog) -> String = "Woof"
Dog.speak: (self: &Dog) -> String = "Bark"  # ❌ エラー:オーバーライドは許可されない

エラーメッセージ:

エラー:Dog.speak(self: &Dog) -> String が重複定義されています
  --> ファイル2:5:1
  |
5 | Dog.speak: (self: &Dog) -> String = "Bark"
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 重複定義
  |
  --> ファイル1:3:1
  |
3 | Dog.speak: (self: &Dog) -> String = "Woof"
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 最初の定義

3.3 ルールの統一 ​

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

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

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

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

4. デフォルト値 ​

中核ルール:フィールドの直後に直接 = value と書き、コンストラクタを省く。

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

コンパイラが生成するコンストラクタ:

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),
}

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

内部宣言と等価である。

5. コンパイラの実装 ​

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

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

5.2 型定義 ​

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

// インターフェース実体化(Self ↦ ConcreteType)
struct InterfaceInstantiation {
    interface: InterfaceId,
    self_type: TypeId,          // Self が置換された具体的な型
    methods: HashMap<MethodId, FunctionBody>,
}

5.3 実装証明 ​

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

5.4 コンパイルフロー ​

1. 型定義を解析し、インターフェース実体化宣言(Animal(Dog))を収集
2. 各インターフェース実体化に対して Self ↦ ConcreteType の置換を実行
3. インターフェースメソッドのシグネチャを展開し、シグネチャ一致をチェック
4. すべてのメソッド定義(内部と外部)を収集
5. シグネチャでグループ化(オーバーロード)
6. オーバーライドをチェック(エラー)
7. インターフェースの完全性をチェック
8. 実装証明を生成

6. 動的ディスパッチ ​

中核設計:コンパイル時の型収集 + インターフェースマッチング、仮想テーブルなし。

6.1 異種コンテナ ​

Animal は (Self: Type) -> Type。List(Animal) は実体化されていないインターフェース型コンストラクタを存在型(existential)として使用する:∃S. Animal(S)——「ある型 S が存在し、S は Animal(S) を実装している」。

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

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

Cat: Type = {
    y: Int,
    Animal(Cat),
    speak: (self: &Cat) -> String = "Meow",
}

# 異種コンテナ — Animal が実体化されていない = 存在型
animals: List(Animal) = [Dog.new(), Cat.new()]
animals[0].speak()  # "Woof"
animals[1].speak()  # "Meow"

所有権セマンティクス:異種コンテナへの挿入は Move セマンティクス(RFC-009)。Dog.new() は AnimalGroup::Dog 列挙バリアントへ Move され、元の変数は使用不可になる。

yaoxiang
dog = Dog.new()
animals: List(Animal) = [dog]
# dog.speak()  ← ❌ コンパイルエラー:dog は move 済み

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(Animal) の最初の構築を検出 → 初期 enum を生成(現在のコンパイルユニット内で既知のすべての構築型)
  2. append / push / インデックス代入ごとに → 値の型がすでに enum にあるかチェック;なければ enum バリアントを拡張
  3. 最終的な enum に対して単態化された match 分岐コードを生成
  4. コンパイルユニットをまたぐ場合:LTO(リンク時最適化)に依存して enum バリアントをマージ。Animal はコンパイルユニットの境界で存在型として渡され、各ユニットが部分的な enum バリアントを生成し、リンク段階で完全な enum にマージされる。

自動生成される enum:

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 点で enum 拡張がトリガ
animals: List(Animal) = [Dog.new(), Cat.new()]
animals.append(bird)                 # コンパイラ:(1) bird が Animal を実装していることを検証 (2) enum を拡張

コンパイラの処理:

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

6.4 ランタイムディスパッチ ​

呼び出しフロー(コンパイル時 enum match、ImplementationProof はすでに消去済み):

animals[0].speak()
  ↓
コンパイラ生成の match:
  match animals[0] {
    AnimalGroup.Dog(d) => d.speak(),
    AnimalGroup.Cat(c) => c.speak(),
    AnimalGroup.Bird(b) => b.speak(),
  }

ブランド投影(RFC-009a との相互作用):match のパターンバインディング AnimalGroup.Dog(d) はブランドツリーに #animals[0].Dog サブブランドを生成し、フィールド投影(#42.field_x)と等価である。d.speak() によって生成される ReadToken(d) ブランド連鎖は animals → animals[0] → d → ReadToken(d) であり、借用チェッカはブランドツリーのプレフィックスマッチングで衝突を検証する。

添字アクセスの型:animals[0] は &AnimalGroup(コンパイラ生成の enum 型)を返し、ユーザは直接 &mut Animal を取得できない。可変アクセスはインターフェースメソッドを介して間接的に実現される(例えば animals[0].mutate() は内部的に AnimalGroup::Dog(d) => d.mutate() に展開される)。

仮想テーブルとの比較:

仮想テーブル(Rust)コンパイル時 enum(YaoXiang)
検索方式仮想テーブルポインタ → メソッドポインタenum match → 直接呼び出し
ランタイムコスト1 回の間接アドレス指定分岐(CPU 分岐予測で最適化可能)
コンパイル時生成仮想テーブルenum + match
ユーザ注釈dyn Trait + 'a が必要不要
ImplementationProof該当なしコンパイル時に消去、ランタイムには存在せず

YaoXiang の優位性:

  • ブランド注釈が不要
  • コンパイル時の型安全性
  • ユーザにとって透過的(dyn Animal を書く必要がない)
  • ImplementationProof は純粋にコンパイル時の概念で、ランタイムコストはゼロ

6.5 制限とスコープ ​

当期(単一コンパイルユニット内): 完全サポート。所有権追跡がすべての append/構築点をカバーし、enum が増分構築される。

コンパイルユニットをまたぐ場合: LTO(リンク時最適化)に依存して enum バリアントをマージする。Animal はコンパイルユニットの境界で存在型(∃S. Animal(S))として渡される。各ユニットが部分的な enum バリアントを生成し、リンク段階でマージされる。

非サポート: ランタイム動的型(完全なダックタイピング)。型の集合はコンパイル時に完全に既知でなければならない。

6.6 実装ノート(フェーズ 3、v1 実装済み) ​

§6 のセマンティクス(異種コンテナ、コンパイル時のメンバチェック、実際の型によるディスパッチ、型の集合のコンパイル時閉じ込め)はすべて実装済みで、実装形態は機構レイヤで次のように具体化されている:

  • 型収集:ImplementationProof に従ってコンパイルユニット全体を一度に収集し、§6.2 の「所有権操作点ごとの増分収集」を置換する。単一コンパイルユニット内では両者は意味的に等価(余分な死バリアントは無害);増分収集の価値はユニットをまたぐケースで発揮され、これは v2 に分類される(後述)。
  • 表現:コンパイラが Animal$Group バリアント型を合成し、これは純粋な IR/バイトコード/ランタイム成果物(命令 CreateVariant/VariantTag/VariantPayload、ランタイム値 RuntimeValue::Enum)であり、MonoType はこれを意識しない——型検査レイヤでユーザに見える型は依然としてインターフェース名。存在型の位置に流入する各具体値は自動的にバリアント値としてラップされる(統一された不透明表現、§6.4 セマンティクス)。
  • ラップ点:typecheck が「具体 vs 存在」の判定位置(注釈付き let/呼び出し実引数/return/リストリテラル要素)で定向的な走査を行い、span キー強制表を生成する。IR 生成は span にしたがってラップを注入する。ラップ漏れはランタイムガードが明確に拒否し(VariantTag/VariantPayload が値が名前付きグループのバリアント値であることを検証)、最悪の場合でもテスト期に明示的なランタイムエラーが発生し、誤ったデータを静かに生成することはない。
  • ディスパッチ:バリアント番号比較のジャンプ連鎖で、各アームがペイロードをアンラップしてから静的呼び出しを行う。RFC-004 の再バインド形式(Type.method = fn[n])はバインド位置に応じて並び替えられた上で同様にディスパッチに参加する。
  • 隔離:レガシートレイト制約(Drawable: Type = {..} 形式、ジェネリックパラメータなし)はバリアントディスパッチを通さず、振る舞いは変わらない。

v1 の境界(後続フェーズ):ユニットをまたぐ LTO バリアントマージ(§6.5);Group 値へのパターンマッチング(match のバリアントパターンに対する IR サポートに依存);リフレクションとの相互作用;コンテナへの Move セマンティクス;Any/型変数の中継フローと推論型ラムダ境界(フォールバック = ランタイムガード)。


ユースケース分析 ​

基本的なインターフェース実装 ​

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

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

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

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

yaoxiang
# 複数のインターフェース
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

Pet: (Self: Type) -> Type = {
    name: (self: &Self) -> String,
}

# 型が複数のインターフェースを実装
Dog: Type = {
    x: Int = 10,
    Animal(Dog),
    Pet(Dog),
    speak: (self: &Dog) -> String = "Woof",
    name: (self: &Dog) -> String = "Buddy",
}

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

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

yaoxiang
# ジェネリックインターフェース
Container: (Self: Type, T: Type) -> Type = {
    add: (self: &mut Self, item: T) -> Void,
    get: (self: &Self, index: Int) -> T,
}

# ジェネリックインターフェースの実装
IntList: Type = {
    data: Array(Int),
    Container(IntList, Int),
    add: (self: &mut IntList, item: Int) -> Void = ...,
    get: (self: &IntList, index: Int) -> Int = ...,
}

異種コンテナ ​

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

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

Cat: Type = {
    y: Int,
    Animal(Cat),
    speak: (self: &Cat) -> String = "Meow",
}

# 異種コンテナ
animals: List(Animal) = [Dog.new(), Cat.new()]

# 使用
for animal in animals {
    print(animal.speak())
}
# 出力:
# Woof
# Meow

プラグインシステム ​

yaoxiang
# インターフェース定義
Plugin: (Self: Type) -> Type = {
    name: (self: &Self) -> String,
    execute: (self: &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. コンパイル時コスト:各インターフェースに対して enum バリアントと match 分岐コードを生成する必要がある
  3. 型の集合:コンパイル時(単一コンパイルユニット内)に完全に既知である必要がある

緩和策 ​

  1. プラグインシステム:コンパイル時のインターフェースマッチングチェックでサポート
  2. 型の集合:所有権追跡、増分構築——各 append/構築点で収集し、全体走査はしない
  3. コンパイルユニットをまたぐ場合:リンク時に enum バリアント集合をマージし、リンク時単態化と機構を共有

代替案 ​

案採用しない理由
impl キーワード構文の複雑度が増す
仮想テーブル(dyn Trait)ブランド注釈('a)が必要
完全なダックタイピングランタイムコスト、型安全でない
enum ラッピング(手動)ユーザの負担が大きい

RFC-009 との関係 ​

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

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

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

  • 動的ディスパッチは実装証明を使用し、ブランド注釈は不要
  • 実装証明はコンパイル時に生成され、ランタイム検索はゼロ
  • dyn Trait + 'a の複雑さを回避

異種コンテナの所有権:

  • List(Animal) への挿入は Move セマンティクス(RFC-009)で、元の変数はアクセス不可
  • 添字アクセス animals[0] は &AnimalGroup(コンパイラ生成の enum)を返し、ブランド投影連鎖は animals → animals[0] → enum_variant → field
  • 可変アクセスはインターフェースメソッドを介して間接的に実現され、&mut AnimalGroup をユーザに公開しない

インターフェース継承 ​

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

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

Pet: (Self: Type) -> Type = {
    Animal(Self),                       # Pet は Animal を継承 — 新しいキーワードなし
    name: (self: &Self) -> String,
}

# Dog が Pet を実装するとき、Animal と Pet のすべてのメソッドを同時に満たす必要がある
Dog: Type = {
    x: Int,
    Pet(Dog),
    speak: (self: &Dog) -> String = "Woof",  # Animal 由来
    name: (self: &Dog) -> String = "Buddy",  # Pet 由来
}

設計原則: 継承は存在するが、濫用は推奨されない。主な組み合わせ方は複数のインターフェース実体化による(Dog: Type = { Animal(Dog), Pet(Dog), ... })。ある型は、それが満たすすべてのインターフェースを直接宣言でき、継承ツリーを通じて表現する必要はない。インターフェース継承は明確な「is-a」階層がある場合にのみ使用する。

コンパイラの処理: 継承連鎖を展開する。Pet(Self) を { Animal(Self) のすべてのメソッド, name: ... } に展開。Dog が Pet(Dog) を宣言するとき、Self ↦ Dog となり、コンパイラは Dog が Animal(Dog) と Pet(Dog) のすべてのメソッドを同時に満たしていることを検証する。

インターフェース継承における Self 置換:Pet: (Self: Type) -> Type = { Animal(Self), ... } において、Animal(Self) の Self は Pet の Self パラメータである——これは遅延置換される。Dog が Pet(Dog) を実装するとき、Self ↦ Dog となり、Animal(Self) は Animal(Dog) になる。これはジェネリック関数の引数渡しセマンティクスと完全に一致する。

デフォルトメソッド実装 ​

インターフェースはメソッドのデフォルト実装を提供できる。実装型は上書きするか、デフォルト実装を継承するかを選択できる:

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

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

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

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

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

コンパイル時の振る舞い: 型がインターフェースを実装するとき、各メソッドについて:

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

設計原則: デフォルトメソッドは Copy/Clone の自動導出メカニズムに似ている——コンパイラが必要に応じて自動生成し、ユーザは上書き可能。virtual/override/super キーワードは導入しない。 ​

実装フェーズ ​

フェーズ内容依存
Phase 1インターフェース宣言構文((Self: Type) -> Type) + Self 型パラメータRFC-011
Phase 2インターフェース実体化(Animal(Dog)) + Self ↦ ConcreteType 置換Phase 1
Phase 3メソッド実装の内部/外部宣言Phase 2
Phase 4オーバーロードとオーバーライド規則Phase 3
Phase 5デフォルト値構文Phase 3
Phase 6インターフェース継承Phase 4
Phase 7デフォルトメソッド実装Phase 6
Phase 8実装証明の生成Phase 7
Phase 9コンパイル時の型収集Phase 8
Phase 10動的ディスパッチ実装Phase 9

設計決定記録 ​

決定決定内容理由日付
インターフェース宣言構文インターフェースはパラメータ化された型 (Self: Type) -> Type、実装時に実体化Self マジックキーワードを排除し、RFC-011 ジェネリクスシステムと完全に統一2026-06-14
Self 型パラメータ明示的な型パラメータ、インターフェース定義時は構文チェックのみ、実体化点で完全チェックHM 推論中の自由型変数を回避2026-06-14
動的ディスパッチコンパイル時の型収集 + 自動 enum 生成仮想テーブルなし、ランタイム検索ゼロ、ユーザにとって透過的2026-06-14
外部メソッド宣言サポート内部宣言と等価の柔軟性、コンパイラがファイル横断収集を担当2026-06-14
オーバーライド禁止(同シグネチャでエラー)オーバーライドは予測不能な振る舞いを生む、オーバーロードがすべてのケースをカバー2026-06-14
インターフェース継承サポート、新しい構文なしインターフェース宣言と同じ構文位置。複数インターフェース実体化(合成)を推奨、深い継承ツリーは非推奨2026-07-03
デフォルトメソッド実装サポート、Copy/Clone 自動導出に類似インターフェースが本体を提供、コンパイラは実装型にインライン化;ユーザは上書き可能。virtual/override を導入しない2026-07-03
デフォルトメソッド制約インターフェース定義時に検証:同じインターフェースのメソッドのみ参照可、上位の実装を仮定不可インターフェースは閉じた契約である。継承は下位の実装を仮定できるが、インターフェースは実装型のフィールド/メソッドを仮定できない2026-07-03
型収集戦略所有権追跡、増分構築——各 append/構築点で収集すべての実装者を全体走査するのではなく、所有権操作点で増分的に enum を拡張2026-07-03
ImplementationProof純粋にコンパイル時の概念、ランタイムで消去ランタイムは enum match ディスパッチで動作、証明はコンパイル時検証にのみ使用2026-07-03
コンパイルユニットをまたぐLTO で enum バリアントをマージ存在型がコンパイルユニットの境界を渡り、各ユニットが部分的な enum を生成、LTO 段階でマージ2026-07-03
フィールド/メソッド名前空間統一名前空間、衝突でエラーフィールドアクセス point.x とメソッド呼び出し point.x() は構文上区別できないため、統一して曖昧さを排除2026-07-03
異種コンテナの所有権Move セマンティクス、コンテナ挿入後は元の変数は使用不可RFC-009 所有権モデルと一致2026-07-03
ブランド投影match パターンバインディングがサブブランドを生成、フィールド投影と等価RFC-009a ブランドツリーメカニズムと一致、enum バリアント投影はブランドツリーの合法パス2026-07-03
レシーバ表記規約&Self 借用 / &mut Self 可変借用 / 値渡し = Moveレシーバはシグネチャのセマンティクス(RFC-009)に従う、インターフェースはデフォルト借用;過去の値渡し表記は &Self に移行2026-08-30

未解決問題 ​

  • [x] インターフェース継承(インターフェースが他のインターフェースを継承できる) → サポート、新しい構文なし。Pet: (Self: Type) -> Type = { Animal(Self), ... }
  • [x] デフォルトメソッド実装(インターフェースがデフォルト実装を提供できる) → サポート、Copy 自動導出に類似。インターフェースが本体を提供し、コンパイラが必要に応じてインライン化
  • [x] 暗黙のマジックキーワードとしての Self → 排除。Self は明示的な型パラメータ、インターフェースは (Self: Type) -> Type
  • [ ] インターフェース制約の高度な使い方(関連型、GAT)—— 関連型はジェネリックインターフェースパラメータで実現(Container: (Self: Type, T: Type) -> Type)、GAT はさらなる設計が必要
  • [ ] クロージャとの相互作用(クロージャがインターフェースを実装)—— 初期戦略:クロージャはインターフェースを直接実装できず、wrapper 型が必要。匿名型のインターフェース実装は後続の RFC に委ねる

参考文献 ​


ライフサイクルと帰趣 ​

状態位置説明
採択済みdocs/design/rfc/accepted/正式な設計文書