RFC-011a: インターフェース実装と動的ディスパッチ
親 RFC: RFC-011: ジェネリクスシステム設計
本 RFC は RFC-011 §2.1-2.4 のインターフェース制約部分を補完し、置換する。
要約
RFC-011 はジェネリクスシステムを定義したが、インターフェース実装の仕組みを詳しく規定していない。本文書では以下を補足する:
- インターフェース宣言:インターフェースはパラメータ化された型——
(Self: Type) -> Type、実装時に具体的な型を渡す - メソッド実装:内部宣言と外部宣言の両方をサポート
- オーバーロード規則:シグネチャが異なればオーバーロードを許可、同じならエラー(オーバーライド禁止)
- デフォルト値:フィールドの直後に直接
= valueと書く - 動的ディスパッチ:コンパイル時の型収集 + インターフェースマッチング、仮想テーブルなし
中核設計:
# インターフェース定義(パラメータ化された型、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 はジェネリクスシステムを定義したが、以下を詳しく規定していない:
| 問題 | 説明 |
|---|---|
| インターフェース宣言構文 | 型がインターフェースを実装していることをどう宣言するか? |
| メソッド実装の配置 | 内部宣言と外部宣言のどちらにするか? |
| オーバーロード規則 | 同名メソッドをどう処理するか? |
| デフォルト値の構文 | フィールドにどうデフォルト値を設定するか? |
| 動的ディスパッチ | 異種コンテナをどう実現するか? |
設計目標
- 簡潔:
implキーワードが不要 - 柔軟:メソッド実装は内部・外部どちらもサポート
- 統一:オーバーロード規則が一貫
- 便利:デフォルト値の構文が簡潔
- ゼロコスト:仮想テーブルなし、コンパイル時の型収集
Rust との比較
| 特性 | Rust | YaoXiang |
|---|---|---|
| インターフェース宣言 | 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 は明示的な型パラメータで、マジックキーワードではない。実装時はインターフェースを呼び出し、具体的な型を渡す。
# インターフェース定義(RFC-011 のジェネリック型と完全に一致)
Animal: (Self: Type) -> Type = {
speak: (self: &Self) -> String,
}
# 型宣言でインターフェースを実装
Dog: Type = {
x: Int,
Animal(Dog), # インターフェースの実体化、Self ↦ Dog
}コンパイラの処理:
Animal(Dog)が(Self: Type) -> Typeの実体化呼び出しであることを識別Self ↦ Dogの置換を実行:Animal(Dog)を展開 →{ speak: (self: &Dog) -> String }Dogが必要なメソッドをすべて提供しているかチェック(シグネチャの一致)- 通過 → 実装証明を生成
- 失敗 → コンパイルエラー
展開の等価性:
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 フィールド名とメソッド名の名前空間
型のフィールド名とメソッド名は同じ名前空間を共有する。インターフェースを展開した後、インターフェースメソッド名と型のフィールド名が衝突した場合、コンパイルエラーとする:
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 内部宣言
Dog: Type = {
x: Int = 10,
Animal(Dog),
speak: (self: &Dog) -> String = "Woof", # メソッド実装は内部
}2.2 外部宣言
Dog: Type = {
x: Int,
Animal(Dog),
}
# メソッド実装は外部
Dog.speak: (self: &Dog) -> String = "Woof"2.3 混在宣言
Dog: Type = {
x: Int = 10,
Animal(Dog),
speak: (self: &Dog) -> String = "Woof", # 一部のメソッドは内部
}
# 一部のメソッドは外部
Dog.play: (self: &Dog) -> Void = { ... }コンパイラの処理:
- すべての定義(内部と外部)を収集
- シグネチャでグループ化(オーバーロード)
- オーバーライドがないかチェック(エラー)
- インターフェースの完全性をチェック
- 実装証明を生成
3. オーバーロードとオーバーライド
中核ルール:
- シグネチャが異なる → オーバーロード → 許可
- シグネチャが同じ → オーバーライド → エラー
3.1 オーバーロード(許可)
# 引数型が異なる場合はオーバーロードを許可
Dog.speak: (self: &Dog) -> String = "Woof"
Dog.speak: (self: &Dog, volume: Int) -> String = "WOOF"3.2 オーバーライド(禁止)
# シグネチャが完全に同じ場合はオーバーライドを禁止
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 ルールの統一
内部宣言と外部宣言は同じオーバーロード/オーバーライド規則に従う:
# 内部宣言
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 と書き、コンストラクタを省く。
Dog: Type = {
x: Int = 10, # デフォルト値
y: Int = 20, # デフォルト値
Animal(Dog),
}コンパイラが生成するコンストラクタ:
# すべてのフィールドにデフォルト値がある → 引数なしのコンストラクタを生成
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),
}
# 外部宣言でデフォルト値を設定
Dog.x: Int = 10
Dog.y: Int = 20内部宣言と等価である。
5. コンパイラの実装
5.1 インターフェースディスクリプタ
// コンパイラ内部:インターフェースディスクリプタ
struct InterfaceDescriptor {
name: String,
self_param: TypeParam, // Self 型パラメータ
methods: Vec<MethodSignature>,
}5.2 型定義
// コンパイラ内部:型定義
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 実装証明
// コンパイラ内部:実装証明
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) を実装している」。
# インターフェース定義
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 され、元の変数は使用不可になる。
dog = Dog.new()
animals: List(Animal) = [dog]
# dog.speak() ← ❌ コンパイルエラー:dog は move 済み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(Animal)の最初の構築を検出 → 初期 enum を生成(現在のコンパイルユニット内で既知のすべての構築型)append/push/ インデックス代入ごとに → 値の型がすでに enum にあるかチェック;なければ enum バリアントを拡張- 最終的な enum に対して単態化された
match分岐コードを生成 - コンパイルユニットをまたぐ場合:LTO(リンク時最適化)に依存して enum バリアントをマージ。
Animalはコンパイルユニットの境界で存在型として渡され、各ユニットが部分的な enum バリアントを生成し、リンク段階で完全な enum にマージされる。
自動生成される enum:
# コンパイラが自動生成(ユーザは認識しない)
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 点で enum 拡張がトリガ
animals: List(Animal) = [Dog.new(), Cat.new()]
animals.append(bird) # コンパイラ:(1) bird が Animal を実装していることを検証 (2) enum を拡張コンパイラの処理:
append引数の返り値型をチェック- その型が対象インターフェースを実装しているかを検証
- 通過 → enum を拡張し、挿入を許可
- 失敗 → コンパイルエラー
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/型変数の中継フローと推論型ラムダ境界(フォールバック = ランタイムガード)。
ユースケース分析
基本的なインターフェース実装
# インターフェース定義
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"複数インターフェースの実装
# 複数のインターフェース
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"ジェネリックインターフェース
# ジェネリックインターフェース
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 = ...,
}異種コンテナ
# インターフェース定義
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プラグインシステム
# インターフェース定義
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()
}
}トレードオフ
利点
- 簡潔:
implキーワードが不要 - 柔軟:メソッド実装は内部・外部どちらもサポート
- 統一:オーバーロード規則が一貫
- 便利:デフォルト値の構文が簡潔
- ゼロコスト:仮想テーブルなし、コンパイル時の型収集
- 型安全:インターフェースマッチングはコンパイル時チェック
- ユーザにとって透過的:
dyn Animal + 'aを書く必要がない
欠点
- 制限:ランタイム動的型(完全なダックタイピング)は非サポート
- コンパイル時コスト:各インターフェースに対して enum バリアントと match 分岐コードを生成する必要がある
- 型の集合:コンパイル時(単一コンパイルユニット内)に完全に既知である必要がある
緩和策
- プラグインシステム:コンパイル時のインターフェースマッチングチェックでサポート
- 型の集合:所有権追跡、増分構築——各
append/構築点で収集し、全体走査はしない - コンパイルユニットをまたぐ場合:リンク時に 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をユーザに公開しない
インターフェース継承
インターフェースは別のインターフェースを含むことができる。新しい構文を導入しない——型がインターフェースを宣言するのと完全に同じ構文位置を使用する:
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) になる。これはジェネリック関数の引数渡しセマンティクスと完全に一致する。
デフォルトメソッド実装
インターフェースはメソッドのデフォルト実装を提供できる。実装型は上書きするか、デフォルト実装を継承するかを選択できる:
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 で宣言されたメソッドを使用できる——継承しているため、存在が保証されるからである。
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 由来
}コンパイル時の振る舞い: 型がインターフェースを実装するとき、各メソッドについて:
- 型が提供 → 型のメソッドを使用
- 型が未提供、インターフェースにデフォルトあり → コンパイラがデフォルト実装を型にインライン化(仮想テーブルコストゼロ)
- 型が未提供、インターフェースにデフォルトなし → コンパイルエラー
設計原則: デフォルトメソッドは 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 に委ねる
参考文献
- RFC-011: ジェネリクスシステム設計 — 親 RFC
- RFC-009: 所有権モデル設計 — 所有権システム
- RFC-009a: 借用証明パイプライン — ブランド機構
- RFC-010: 統一型構文 — 統一構文
ライフサイクルと帰趣
| 状態 | 位置 | 説明 |
|---|---|---|
| 採択済み | docs/design/rfc/accepted/ | 正式な設計文書 |
