---
title: 'RFC-011b: 演算子オーバーロードとインターフェース駆動演算子'
status: '採用済み'
author: '晨煦'
created: '2026-09-15'
updated: '2026-09-22'
group: 'rfc-011'
issue: '#341'
---RFC-011b: 演算子オーバーロードとインターフェース駆動演算子
参考:
- RFC-011: ジェネリクスシステム設計 — 型制約
T: Add + Multiply、関連型- RFC-011a: インターフェース実装と動的ディスパッチ — インターフェース宣言/インスタンス化機構
- RFC-009: 所有権モデル設計 —
&mut T線形トークン- RFC-004: カリー化メソッドの複数位置結合バインディング —
f[0]位置バインディング構文- RFC-010: 統一型構文 — 和型 = 全フィールドが自身を返すレコード
- RFC-013: エラーコード仕様 —
Resultの std 帰属の既存位置付け、E108x- RFC-010b: パターンマッチングの完全化(バリアント分解と網羅性) — バリアント分解(依存)
要約
YaoXiang に演算子オーバーロード能力を補完し、a + b / a == b / a[i] / e? をユーザ定義型が実装できるようにし、RFC-011 で既に書き込まれた制約構文中の演算子制約(T: Add + Multiply)を 紙の上の存在から着地可能な機構(同一文中の Zero は演算子に属さない、オープン問題参照)へと変える。
設計は三層の責務分離を採用する:固定された演算子→メソッドマッピング表(Layer 0)、名前でディスパッチする基盤(Layer 1、既存の method_bindings を再利用)、インターフェース契約層(Layer 2、generics 制約用)。演算子の優先順位と結合性は言語で固定され、ユーザはセマンティクスのみをオーバーロードする。
初回対象範囲:Add Subtract Multiply Divide Modulo Equal Index の七つのインターフェース。算術インターフェースは三つの型パラメータ (Self, R, O) を採用する——結果型 O を明示化することで、1 + 2.5(Float を返す)や point * 2.0(スケール)といった異種型演算を表現可能にする。Equal はデフォルトで自動派生され(全フィールドが比較可能なレコードは自動的にフィールド毎の == を獲得)、明示的なインスタンス化で上書き可能。
Try(? のインターフェース化)は全体としてフェーズ 2 に移す:これは RFC-010(構築)と RFC-010b(分解)の着地に依存し、なおかつインターフェース形状が未確定である(オープン問題参照)。
新しい構文もキーワードも追加せず、すべて RFC-011a の既存機構(インターフェース宣言 / インスタンス化 / 外部メソッド宣言 / オーバーロード)を再利用する。
動機
なぜこの機能が必要か
1. RFC-011 の中心的な例がこれに依存し、現状は「履行不能」より悪い
RFC-011(採用済み)は 8 箇所で演算子名を型制約として用いている:
multiply: (T: Add + Multiply + Zero, Rows: Int, Cols: Int, M: Int) -> (
(a: Matrix(T, Rows, Cols), b: Matrix(T, Cols, M)) -> Matrix(T, Rows, M)
)RFC 全体を通じて Add / Multiply がどこから来るのか、+ がどうそれらに束縛されるのかは一度も定義されていない。実測で確認すると現状はさらに悪い:T: Add は今日直接コンパイルエラーになる——制約解決が旧 trait 表(trait_data.rs)に問い合わせるが、その表に Add は無い。制約名 Zero / One / PartialOrd / Fn / FnMut も同様に宙に浮いている(本 RFC での扱いは「他の RFC との連携」参照)。
2. ユーザ定義型が基本演算に参加できない(実測)
Point: Type = { x: Int, y: Int }
a = Point(1, 2)
b = Point(1, 2)
a == berror [E6007] Runtime error: type mismatch in comparison Eq:
Struct { type_id: TypeId(0), ... } vs Struct { type_id: TypeId(0), ... }連鎖的結果:list.contains(list_of_structs, p) は完全に使用不能——内部で == に依存する。一方で (1, 2) == (1, 2) と [1] == [1] はランタイムでの要素毎比較でずっと動作してきた——構造体だけが唯一の穴である。
3. ? が型名をコンパイラーに直付けし、Result の std 帰属を阻む
? の実装は型名とバリアント番号の両方をハードコードしている:
// typecheck:ハードコードで Result 型を構築
let expected_result = MonoType::make_result(ok_ty, expected_err);
// ir_gen:ハードコードでグループ名とバリアント番号
Instruction::VariantTag { group: "Result".to_string(), .. }
variant 0 = ok, variant 1 = errこれにより Result は core に留まらざるを得ない。仮に ? をインターフェース駆動にすれば、当該インターフェースを実装した任意の型(ユーザ定義型を含む)が ? で使用可能となり、Result は std に帰属させ得る(RFC-013 には既に「std ライブラリ Result(T, Error)」の位置付けがある)。
これは問題の一側面に過ぎないことに注意:ok(...) / err(...) / some(...) といった構築構文も現在パーサーに直付けされている(言語仕様 §1.4.2 で「パーサーが認識するコンストラクタ」として列挙)。「Result の std 帰属」には ? とコンストラクタを両側同時に解放する必要があり、どちらも本 RFC のフェーズ 2 に帰属する。
4. ユーザ定義コンテナがインデックス化できない
Index の型検査はハードコードされたホワイトリストである:
// expressions.rs
MonoType::Generic { name, args } if name == "List" => Ok(args[0].clone()),
MonoType::Generic { name, args } if name == "Array" => Ok(args[0].clone()),
MonoType::Generic { name, args } if name == "Dict" => Ok(args[1].clone()),
// それ以外 → "型層で認めないコンテナは静かに拒否せず明示エラー"ユーザが任意のコンテナ型を定義すると、c[0] は一律エラーになる。
5. % の実装が既刊ドキュメントに違反している
実測で -7 % 3 は -1(切り捨て余り)を返す。しかし言語リファレンスの演算子優先順位表(reference/index.md)には既に「* / % 乗除剰余」と書かれている——ドキュメントは剰余を約束し、実装は余りを返している。これは設計変更ではなく、実装がドキュメントに違反する欠陥であり、本 RFC で併せて修正する。
既存の半完成の基礎
調査の結果、機構の大部分は既に存在し、欠けているのは配線であることが判明した:
| 機構 | 位置 | 状態 |
|---|---|---|
インターフェース宣言 (Self: Type) -> Type | RFC-011a Phase 1 | ✅ 動作可能 |
インターフェースインスタンス化 Animal(Dog) + 外部メソッド宣言 | RFC-011a Phase 2–3 | ✅ 動作可能(単体テストあり) |
メソッドディスパッチ method_bindings["Type.method"] | expressions.rs(environment.rs で登録、expressions.rs の呼び出し解析とフィールドフォールバック経路で照会) | ✅ 動作可能 |
| 関連型(= インターフェース型パラメータ) | RFC-011a §3.1;RFC-011a はこの方案を既採用 | ✅ 機構確定 |
型族評価 AssociatedTypeDef | dependent_types.rs | ✅ 本番ユースケース std.assert の IsTrue |
Equal / Dup / Clone / Debug trait | trait_data.rs に既登録 | ⚠️ Equal 消費点ゼロ;旧自動派生はシグネチャ登録のみで実装なし |
| 演算子 → メソッド名マッピング | — | ❌ 存在しない |
結論:これはゼロから機能を作るのではなく、既存部品を配線して文書化することである。
提案
中核設計:三層の責務分離
Layer 0 固定マッピング表(言語レベル定数、ユーザ変更不可)
+ → add == → equal [] → index ? → residual
│ コンパイル時に組み込み、型推論に参加せず、ユーザに非公開
▼
Layer 1 ディスパッチ基盤(名前でメソッドを検索し呼び出す) ← 既存機構を再利用
method_bindings["Point.add"]
▼
Layer 2 インターフェース契約(generics 制約用)
Add / Equal / Index / Try …
T: Add 制約を成立させ、かつ演算子の通行条件とする階層化の理由:
- Layer 0 と 1 が演算子を「使える」ようにし、Layer 2 が演算子を「制約できる」ようにする。統合すると
addという名前のあらゆるメソッドが+から呼ばれることになり、RFC-011 のT: Addが演算子と切り離される。 - Layer 1 を新設しない:
method_bindingsは既にType.methodをキーに検索しており(フィールド検索失敗後のフォールバック経路)、演算子も同じ経路を通せばよい。 - Layer 2 は通行条件:演算子の通行前に必ず型が対応インターフェースを実装していることを確認する(§Equal の自動派生例外——派生と登録は同じ判定基準)。
名前と登録:二つの独立した経路
演算子は「インターフェース実装登録表」を照会し、通常の名前解決を経由しない。
- 登録表はコアが所有し、二つの登録を統合する:コアが基本型に行う既定登録(
Add(Int, Int, Int)など)、およびユーザが型本体に書くインターフェースインスタンス化(Add(Point, Point, Point))。実装コードが物理的にどのモジュールにあっても、登録は同じ一つの表に集約され、+/==/[]はこの表のみを参照する。 - ローカルモジュールで
Addという同名のバインディング(例えば RFC-011 §5.2 の Peano 型レベル加算Add: (A: Type, B: Type) -> Type = match ...)を定義しても、それは名前検索経路を通り、当該モジュール内のAddという名前の解決にのみ影響し、登録表には触れず、演算子の可用性にも影響しない。同名異物、相互に隠蔽しない。
これは同時に「RFC-011 の型レベル Add と本 RFC のインターフェース Add が衝突するか」に対する答えでもある:衝突しない。型レベル Add は純粋に型レベルの計算であり(Zero/Succ は型であって値であり、絶対に Zero + Succ(...) の値演算は存在しない)、値レベルの演算子インターフェースとは同じ名前の二つの層である。
Layer 0:固定マッピング表
| 演算子 | インターフェース | メソッド | 初回 |
|---|---|---|---|
+ | Add | add | ✅ |
- | Subtract | subtract | ✅ |
* | Multiply | multiply | ✅ |
/ | Divide | divide | ✅ |
% | Modulo | modulo | ✅ |
== != | Equal | equal | ✅ |
[] | Index | index | ✅ |
? | Try(形状未確定、オープン問題参照) | residual(暫定) | ❌ フェーズ 2 |
< <= > >= | —(ネイティブ命令として保持) | — | ❌ |
and or | —(短絡は言語セマンティクス、オーバーロード不可) | — | ❌ |
| ビット演算 5 個 | — | — | ❌ |
単項 - ! | — | — | ❌ |
インターフェース名は省略形ではなくフルスペル(Mul ではなく Multiply):RFC-011 本体の T: Add + Multiply + Zero との一貫性を保ち、既採用の RFC を改変しない。
% は Modulo セマンティクス(数学的剰余、結果の符号は除数に従う)を採用する。動機セクションで既述の通り、現在の remainder 実装は既刊ドキュメント(reference/index.md「乗除剰余」)に違反しており、本条は欠陥修正として扱い、互換期間は設けない。
現状注記:% の型検査ホワイトリストは現状 Int/Float のみ(+ の Int/Float/String/List ホワイトリストとは異なる)、付録 A.2 に実測記録あり。
Layer 2:インターフェース定義
算術インターフェース(三つの型パラメータ)
Add: (Self: Type, R: Type, O: Type) -> Type = {
add: (self: &Self, other: &R) -> O
}
Subtract: (Self: Type, R: Type, O: Type) -> Type = {
subtract: (self: &Self, other: &R) -> O
}
Multiply: (Self: Type, R: Type, O: Type) -> Type = {
multiply: (self: &Self, other: &R) -> O
}
Divide: (Self: Type, R: Type, O: Type) -> Type = {
divide: (self: &Self, other: &R) -> O
}
Modulo: (Self: Type, R: Type, O: Type) -> Type = {
modulo: (self: &Self, other: &R) -> O
}三つの型パラメータはそれぞれ固有の役割を果たす:
Self:左オペランド型(レシーバ、&Selfで借用、RFC-009 借用トークンと RFC-011a レシーバ規約参照);R:右オペランド型——左右で異種型を許容;O:結果型、明示的に宣言される。
なぜ結果型を明示パラメータにしなければならないのか(-> Self に固定するのではなく):
Selfに固定するとAdd(Int, Float)のメソッドはIntを返さねばならず、1 + 2.5を正しく表現できない;- 結果型を明示化すれば、RFC-011 §8.3 の昇格型族
Add: (A, B) -> Type = match (A, B) { (Int, Float) => Float, ... }と本 RFC のインターフェース登録は同じ表の二つのビューとなる——コア登録Add(Int, Float, Float)こそがまさに(Int, Float) => Floatの行であり、ユーザのインスタンス化はそれぞれこの表への行追加となる; Indexインターフェース本来就是三参数(Self, Key, Value)、戻り型Valueをパラメータとする——算術インターフェースもOを加えれば、演算子インターフェースファミリの形状が統一され、却ってSelf固定の方が異質となる。
コア既定登録(ネイティブ命令経路、メソッド呼び出しを経由しない):Add(Int, Int, Int)、Add(Int, Float, Float)、 Add(Float, Int, Float)、Add(Float, Float, Float)、Add(String, String, String)(連結)、 Add(List(T), List(T), List(T))(要素連結)など、五つの算術インターフェースは基本型に対しても同様。1 + 2.5 は現状のコンパイルエラー(ホワイトリストが両側同型を要求)から、3.5: Float を返す合法な演算へ変わる。
制約構文糖衣:T: Add ≜ 既に登録された Add(T, T, T)——同型自己複合、結果もその型。RFC-011 行列乗法の例で a * b + c の全工程が型 T であるのは、まさにこの意味を要求している。T: Equal も同様 ≜ Equal(T, T)。
異種型例——ベクトルスケール:
Point: Type = {
x: Float,
y: Float,
Multiply(Point, Float, Point),
}
Point.multiply: (self: &Point, other: &Float) -> Point =
Point(self.x * other, self.y * other)
main: () -> Void = {
p = Point(1.0, 2.0)
q = p * 3.0 # Point(3.0, 6.0)
}等価インターフェース(デフォルト自動派生)
Equal: (Self: Type, R: Type) -> Type = {
equal: (self: &Self, other: &R) -> Bool
}Equal はデフォルトで自動派生され、規則は五つ:
- デフォルト派生:レコード型を定義する際、全フィールドが比較可能(基本型、
String、もしくはそれ自体が比較可能なレコード/タプル/リストであり、&mutフィールドを含まない)であれば、コンパイラーは自動的にフィールド毎比較の==を生成し、ネイティブコード経路を走行し、ユーザ可視のメソッドを生成しない。ユーザが自分で書いた型は自然に比較可能であり、儀式は不要。 - 明示的オーバーライド:型本体に
Equal(Point, Point)を書きPoint.equalメソッドを提供した場合、ユーザのバージョンが用いられる(例:許容差付き浮動小数点比較)、自動派生は行われない。 - フィールドが比較不可能な場合:自動派生は失敗し、
==は使用不可となり、診断がどのフィールドが障害かを指摘する;この場合ユーザは手動でEqualを書いて比較をカスタマイズ可能(例:関数フィールドを名前で比較)。 - 前置条件:
&mut T線形トークン(フィールド再帰)を含む型は比較に参加しない——線形トークンは一度読むと消費され、二つの値を同時に取り出して比較できない。前提が「線形性」であり「Dup」ではないことに注意:プリミティブ値型(Int/Float/Bool/Char)は RFC-011 §2.4 により Dup に属さず(これらはコンパイラー組み込みの値コピー)、Dup を前提とするとPoint { x: Float, y: Float }を誤って拒否することになる。 - 制約と同じ判定基準:
T: Equalの解決と==の通行は同じ「登録表照会または構造派生」規則を走行する——制約と演算子は常に同じ答えを出す。
一貫性の根拠:(1, 2) == (1, 2) と [1] == [1] は今日すでにランタイム要素毎比較を行い(executor.rs の比較ホワイトリストに Tuple/List/Array を含む)、レコード型のみが除外される複合型である。自動派生は新しい暗黙のデフォルトではなく、言語内部の既存挙動を最後の欠片まで補完するものである。
旧機構の廃止:trait_data.rs 中の Equal の旧自動派生(シグネチャ登録のみ、実装コードなし、消費点ゼロ)を停止;Equal の全判定(基本型のデフォルト登録、構造派生、明示的インスタンス化)はインターフェース登録表経由となる。旧 trait 表は Clone/Dup/Debug の既存責務を維持する(名前は演算子インターフェースと重ならず、将来の統合は別途議論)。
インデックスインターフェース
Index: (Self: Type, Key: Type, Value: Type) -> Type = {
index: (self: &Self, key: &Key) -> Value
}Value は関連型メンバーではなく型パラメータ:RFC-011a のオープン問題は「関連型は generics インターフェースパラメータで実現する」と既決定(Iterator: (Item: Type) -> Type が同型先例)、type メンバー構文を導入する必要はない。
所有権に関する注記:index は &Self 借用から完全な Value を返す。標準ライブラリの list.get: (&Vec(A), Int) -> A は既にかつての形態であり、本インターフェースは標準ライブラリの現状と同待遇;移動セマンティクス要素型について「借用から完全な値を取り出す」精確なセマンティクスは RFC-009 の全面施行時に統一処理され、本 RFC はこのために新ルールを発明しない。
複数位置インデックスはタプル詰め合わせ + オーバーロードで実現し、可変長インターフェースは導入しない:
// 一次元コンテナ
List(T) が Index(List(T), Int, T) をインスタンス化 → arr[0]
// 多次元コンテナ
Grid が Index(Grid, Tuple(Int, Int), Float) をインスタンス化 → g[0, 1]
// └─ Key はタプル
// 二つのインスタンス化シグネチャが異なる → 共存同型の同名インターフェースは複数のインスタンス化を許可し、注入されるメソッドのシグネチャで区別、RFC-011a のメソッドレベルオーバーロード規則に従って共存する。RFC-011a のオーバーロードはメソッドレベルまでしか明文化していないが、本条はインスタンス化レベルの規則を一段上に追加する:同名インターフェースインスタンス化共存の合法性は展開後のメソッドシグネチャが衝突するかで決定(衝突すれば E1097、フィールド/メソッド名前空間規則と同一の源)。
伝播インターフェース(フェーズ 2、形状未確定)
? のインターフェース化目標インターフェース名は Try だが、完全な形状は未確定であり、初回には含めない。理由:
?は実際には三つのことを行う:成否判定(現状はハードコードの variant 0 に依存)、成功ペイロードの取り出し、失敗経路での値全体の現在関数からの返却。residual: (self: &Self) -> Eという単一メソッドでは三つ目の側面しかカバーせず、現状固定で書けば将来高い確率で手戻りとなる;- フェーズ 2 はそもそも RFC-010(
Result値の構築)と RFC-010b(バリアント分解)の着地に依存する; - 付随するコンストラクタ直付け問題(
ok/err/someはパーサーが認識、言語仕様 §1.4.2)と?の型名直付けは同じ事柄の二側面であり、「Result の std 帰属」は両側面を同時に解放する必要があり、いずれもフェーズ 2 範囲に帰属する。
例
ユーザ定義型:算術と等価がそのまま使える
Point: Type = {
x: Float,
y: Float,
Add(Point, Point, Point),
}
Point.add: (self: &Point, other: &Point) -> Point =
Point(self.x + other.x, self.y + other.y)
main: () -> Void = {
a = Point(1.0, 2.0)
b = Point(3.0, 4.0)
c = a + b # Point(4.0, 6.0)
println(a == b) # false —— Equal 自動派生、インスタンス化不要
println(a == a) # true
}カスタム等価(自動派生を上書き)
Vec3: Type = {
x: Float,
y: Float,
z: Float,
Equal(Vec3, Vec3),
}
# 浮動小数点比較に許容差を付与、フィールド毎自動派生を上書き
Vec3.equal: (self: &Vec3, other: &Vec3) -> Bool =
abs(self.x - other.x) < 0.000001
and abs(self.y - other.y) < 0.000001
and abs(self.z - other.z) < 0.000001カスタムコンテナインデックス
Box: (T: Type) -> Type = {
data: List(T),
Index(Box(T), Int, T)
}
Box.index: (T: Type)(self: &Box(T), key: &Int) -> T =
list.get(self.data, key)
main: () -> Void = {
b = Box([10, 20, 30])
println(b[1]) // 20
}generics 制約がついに履行可能に
// RFC-011 の例がついに着地可能
// T: Add ≜ Add(T, T, T) が既登録;T: Multiply ≜ Multiply(T, T, T) が既登録
combine: (T: Add + Multiply)(a: T, b: T, c: T) -> T =
a * b + c(RFC-011 の看板例 T: Add + Multiply + Zero 中の Zero / One は本 RFC の範囲外——これらは定数メンバーであり演算子ではなく、「レシーバなしのインターフェースメンバー」は RFC-011a に先例がなく、オープン問題参照。)
構文変更
新しい構文もキーワードも追加しない。全能力は既存機構の組み合わせで実現:
| 能力 | 再利用する既存機構 |
|---|---|
| インターフェース宣言 | RFC-011a Phase 1 |
| インターフェースインスタンス化 | RFC-011a Phase 2(Dog: { Animal(Dog) }) |
| メソッド実装 | RFC-011a Phase 3(外部宣言 Point.add) |
| 関連型 | RFC-011 §3.1(インターフェース型パラメータ) |
| 複数位置インデックス | 既存タプル詰め合わせ解析 + インスタンス化レベルオーバーロード規則 |
| 演算子優先順位 | 言語固定、ユーザカスタマイズ不可 |
詳細設計
型システムへの影響
新規インターフェース(Layer 2):Add Subtract Multiply Divide Modulo EqualIndex(Try はフェーズ 2)。
インターフェース実装登録表:演算子の判定中枢。コア既定登録(基本型)とユーザインスタンス化(check_interface_instantiation が生成する ImplementationProof)を集約し、+ / == / [] の通行検査と T: Add 制約解決(bounds.rs の check_trait_bounds)は同じ表を照会する——「制約は可、演算子は不可」の隙間は存在しない。
Equal の構造派生:登録表の上に構造規則を重ねる(全フィールド比較可能 ⇒ 比較可能)、基本型はコア登録がフォールバック、帰納的に閉包。旧 trait_data.rs の Equal 登録と旧自動派生は停止。
演算子照会と名前解決の分離:+ などの演算子は登録表のみを照会し、通常名前解決を経由しない;ローカル同名のバインディング(例:型レベル Add ファミリ)は演算子に影響しない(§名前と登録 参照)。
オーファンルール:演算子実装は型の定義モジュール内にのみ書ける——Int の定義はコアにあるため、Int の登録はコアのみが書ける;Point の定義はユーザモジュールにあるため、定義者のみが登録可能。全型が同一規則に従い、組み込み型に特権はない。
ランタイム挙動
ランタイムオーバーヘッドゼロ:演算子はコンパイル時に呼び出し対象を決定(静的ディスパッチ)。基本型(Int/Float/String/List)はネイティブ命令ファストパスを保持し、インターフェースディスパッチを経由しない;自動派生された構造体の == はネイティブのフィールド毎比較を生成;Any(動的型)位置の == / != は既存のランタイム比較を保持(RFC-036 テストフレームワーク assert_eq がこの挙動に依存、影響なし)。
% セマンティクス修正:-7 % 3 は -1(切り捨て余り)から 2(数学的剰余)へ。インタプリタ(checked_rem)、定数畳み込み(a % b)、バイトコード(I64_REM)の三箇所を同期して修正。
コンパイラー変更
| コンポーネント | 変更 |
|---|---|
typecheck/inference/expressions.rs | infer_binary ホワイトリストを「ネイティブファストパス + 登録表照会」二経路に;Expr::Index ホワイトリストにインターフェース照会ブランチを追加 |
typecheck/inference/bounds.rs | check_trait_bounds で演算子インターフェース名はインターフェース登録表を照会(Equal は構造派生を含む) |
typecheck/checker.rs | Equal 構造派生(レコード定義後)を追加;インスタンス化レベルオーバーロード規則(同名インターフェース複数インスタンス化はメソッドシグネチャで共存) |
middle/core/ir_gen.rs | Expr::Index にメソッド呼び出しディスパッチブランチを追加;明示的 equal のない構造体 == はネイティブのフィールド毎比較を生成;Mod 命令を数学的剰余に変更 |
| インターフェース登録表(新規) | Layer 0 マッピング表定数 + 演算子→インターフェース照会関数 + コア既定登録(Int/Float/String/List 等) |
trait_data.rs | Equal 登録と旧自動派生を停止(Clone/Dup/Debug は現状維持) |
| 診断 | Equal 前置(線形トークン)不充足は E1101(型がインターフェース未実装)系を流用;E1081/E1082 文言から "Result" の字を削除(フェーズ 2 で Try 着地後、RFC-013 フローに従い三方面同期) |
後方互換性
基本型演算は不変:1 + 2、"a" + "b"、[1] + [2]、1 < 2 はすべてネイティブ経路を保持し、挙動と性能は不変。
1 + 2.5 がコンパイルエラーから合法へ:コア登録 Add(Int, Float, Float) により、以前はホワイトリストに拒否されていた混合算術が利用可能となり、Float を返す。これは新規能力であり、既存コードへの影響なし。
% セマンティクス修正:-7 % 3 は -1 から 2 へ。欠陥修正として位置付け(ドキュメントは既に剰余を約束、動機 §5 参照)、互換期間は設けない;既存テストコーパスに負数 % 依存の用例は存在しない(確認済み)。
構造体 == がランタイムエラーから使用可能へ:以前 Struct == Struct はすべて E6007 ランタイムエラー、配線後は自動派生により使用可能に——悪いから良いへ、既存の合法コードへの影響なし。
Any の == は影響なし:動的型位置はランタイム比較を保持(RFC-036 の assert_eq アサーション系が依存、以前使用可能、以後も使用可能)。
f[0] 位置バインディングは不変:distance[0] は RFC-004 のコンパイラー組み込み能力であり、Index インターフェースを経由せず、影響なし。
? は既存コードに対して透過(フェーズ 2):Result に Try インスタンス化を追加後、既存の ? の挙動は完全に同一。
トレードオフ
利点
- RFC-011 の演算子制約を履行:
T: Add + Multiplyを紙の上の存在(実際はコンパイルエラー)から着地可能へ、既採用 RFC 本体は改変せず Resultの core バインドを解除(フェーズ 2):?とコンストラクタをインターフェース化した後Resultを std に帰属可能、RFC-013 の既存位置付けと一致- 実測欠陥を修正:
Point == Pointがそのまま使え、list.containsの構造体不可用も連鎖的に解消 - 新構文ゼロ:すべて RFC-011a の既存機構を再利用し、パーサー構文規則に触れない
- ランタイムオーバーヘッドゼロ:静的ディスパッチ + 基本型ネイティブファストパス
- ユーザ定義コンテナが利用可能:
Box(T)[0]が「一律エラー」から使用可能へ - 言語内部の一貫性:Tuple/List は既に要素毎比較、レコード型も補完;算術インターフェース三つの型パラメータと Index の形状統一;RFC-011 §8.3 昇格表とインターフェース登録が合一
欠点
Equal自動派生は暗黙のデフォルト:将来「レコードはデフォルトで比較可能」を撤回は破壊的変更となる。この立場を採用する——Tuple/List の既存挙動と一致し、かつ明示的インスタンス化で常に上書き可能Equal前置は線形トークンを含む型を拒否:&mutフィールドを含む型は==不可、これは意味的に正しいことの代償として、明確な診断(どのフィールドか)が必要- インターフェースインスタンス化は明示的コスト:算術演算子ごとにインスタンス化とメソッドを一行ずつ書く必要(
Equalは免除——自動派生)。RFC-011a の構文が暗黙派生を許さない(代わりにSelf型パラメータに魔法がない) %セマンティクス修正は挙動変更:欠陥修正として位置付けるも、マイグレーション説明に明記が必要
代替案
方案 A:Layer 1 のみ実装(メソッド名ディスパッチ)、インターフェース層なし
+ は add という名前のメソッドがあるかだけを照会し、Add インターフェース実装を要求しない。
却下理由:RFC-011 の T: Add 制約が演算子と切り離される——制約はインターフェースを照会、演算子はメソッド名を照会し、両者が矛盾する答えを出す可能性がある。かつ「+ を加算不可能な型に適用」に対してコンパイル時に正確な診断を提供できない。
方案 B:? 問題解決のためコンストラクタ構文 Ok(x) / Some(x) を導入
? をインターフェース化せず、和型にコンストラクタ構文を追加する。
却下理由:RFC-010(既採用)と衝突。RFC-010 は「和型表現としてレコード型を一律使用し、二組の構文は不要」と明確に規定し、| 構文を明示的に廃止している。コンストラクタ構文の導入は第二の表現系の導入となる。かつこれは ? のみを解決し、Point == Point やカスタムコンテナインデックスは解決しない。
方案 C:比較演算子も初回にインターフェース化(Ordering の導入)
< <= > >= を Compare インターフェース化し、三値 Ordering を返す。
却下理由:< は YaoXiang では既に第一級 IR 命令(Instruction::Lt/Le/Gt/Ge)であり、インターフェース化すると基本型が迂回を強いられる。かつ Ordering を導入すると Ordering 自身の比較/ソート、浮動小数点 NaN の PartialOrd vs Ord など一連の問題が派生し、独立 RFC の規模となる。現状の実測で露呈した要件(Point == Point、list.contains)は Equal のみで十分。
方案 D:演算子名は省略形(Add インターフェース内のメソッドは add、インターフェース名は Mul)
却下理由:RFC-011 本体に既に T: Add + Multiply + Zero と書かれており、省略形採用は既採用 RFC の改変を伴う。
方案 E:Equal は明示的インスタンス化のみ、自動派生なし
却下理由:ユーザが自分で書いた型が == できないという体験は容認不可;かつ (1, 2) == (1, 2)、[1] == [1] は今日すでに要素毎比較、レコード型のみが除外されており、元来一貫していない。明示的インスタンス化は上書き手段として保持し、カスタム要求を両立する。
方案 F:算術インターフェースの戻り型を -> Self に固定
却下理由:Add(Int, Float) のメソッドは Int を返さねばならず、1 + 2.5 を正しく表現できない;ベクトルスケール Multiply(Point, Float) も同じ理由で書けない。かつ RFC-011 §8.3 の昇格型族 (Int, Float) => Float が着地位置を失う。三つの型パラメータ (Self, R, O) は Index(Key, Value) と形状が統一される。
実装戦略
依存関係
| 依存 | 状態 | 本 RFC への影響 |
|---|---|---|
| RFC-011a インターフェース機構 Phase 1–3 | ✅ 着地済み | Layer 2 の地盤(実測で動作可能) |
| RFC-010 レコード式構築経路 | ❌ 未着地 | フェーズ 2:? の構築側 + コンストラクタ parser 特例廃止 |
| RFC-010b バリアント分解 | ❌ 紙の上 | フェーズ 2:Result.residual の match 記法、網羅性 |
| RFC-009 線形トークン推論 | 一部 | Equal の前置制約検査 |
段階分け
インターフェース依存(設計制約でありスケジュールではない)で二組に分割:
フェーズ 1 — RFC-010/010b に依存しない:
- Layer 0 マッピング表 + インターフェース実装登録表 + Layer 1 ディスパッチ配線
AddSubtractMultiplyDivideModuloEqualIndex七つのインターフェース(三つの型パラメータ形態)Equal自動派生 + 線形前置制約 + 制約解決を登録表経由に切り替え%セマンティクス修正(インタプリタ/定数畳み込み/バイトコードの三箇所)Any位置の==はランタイム比較を保持- 効果:
Point + Point、Point == Point(儀式不要)、Box(T)[0]、1 + 2.5、T: Add制約がすべて使用可能
フェーズ 2 — RFC-010 / RFC-010b に依存、着工前に Try 形状を確定:
Tryインターフェース形状確定(オープン問題参照)?インターフェース化 + コンストラクタ(ok/err/some)parser 特例廃止、RFC-010 レコード構築経路に切り替えResultを core から std へ移行(RFC-013 の既存位置付けに基づく)
リスク
| リスク | 緩和策 |
|---|---|
% セマンティクス変更が既存ユーザコードに影響 | 欠陥修正として位置付け + テストコーパスに負数 % 依存なしの既確認 |
Equal 自動派生の暗黙デフォルトが将来回収困難 | 立場確定:Tuple/List の既存挙動と一致、明示的インスタンス化で上書き可能 |
| 基本型の二経路(ネイティブ + 登録)の不整合 | ゲート:コア登録のセマンティクスはネイティブ命令と一致必須(同じ表の二つのビュー) |
| 登録表照会でコンパイルが遅延 | 型名によるインデックス + インスタンス化結果キャッシュ(RFC-011a proof を再利用) |
| インスタンス化レベルオーバーロードが曖昧性導入 | メソッドレベルオーバーロードと同一規則:シグネチャ衝突は E1097 |
他の RFC との連携
本 RFC の位置付けは消費側要件提起者であり、以下 RFC を同期更新して連携整合を図る(改訂権限取得済み):
| RFC | 更新内容 |
|---|---|
| RFC-011(既採用) | §制約箇所に Add / Multiply は 011b で定義・着地されることを注記(T: Add ≜ Add(T, T, T));Zero / One / PartialOrd / Fn / FnMut を宙に浮いた制約名として標記(後続 RFC を待つ);§8.3 昇格型族はインターフェース登録表との合一を注記 |
| RFC-010(既採用) | 「レコードフィールドがすなわちコンストラクタ」の着地要件を明確化——フェーズ 2 コンストラクタ parser 特例廃止の前提 |
| RFC-010b(ドラフト、元 RFC-039) | 構築と分解はペアでなければならない;網羅性判定は Result の core/std 帰属に依存しない(011b フェーズ 2 で移行) |
| RFC-009(既採用) | §型属性箇所で交差参照:Equal 前置は「&mut 線形トークンを含まない」(Dup ではない) |
| RFC-013(既採用) | フェーズ 2 着地時:E1081 / E1082 文言から "Result" を削除;Equal 前置診断は E1101 系を流用——RFC-013 フローに従い三方面同期(codes/*.rs ↔ locales ↔ コード表) |
| RFC-018(既採用) | % を数学的剰余に変更後、Mod → srem/urem マッピング表が無効化、srem + 符号修正(または sdiv+mul+sub 合成)に変更、「剰余/余り」用語混在の修正が必要 |
| RFC-036(既採用) | 改変不要。関係明記:Any 位置の == はランタイム比較を保持、assert_eq アサーション系はゲート影響なし |
Resultの std 帰属の根拠:RFC-013 には既に「std ライブラリResult(T, Error)」と位置付け、RFC-014 の階層化で std はコアソースに帰属する。本 RFC フェーズ 2 はその着地経路の一つであり、他番号は引用しない。
オープン問題
- [x]
→ クローズ:言語リファレンスに既に乗除剰余と明記、現状 remainder 実装は既刊ドキュメントに違反、欠陥修正として処理、互換期間なし(2026-09-22)Moduloのセマンティクス修正に互換期間が必要か? - [x]
ユーザが既存型に演算子実装を追加できるか(オーファンルール)?→ 確定:演算子実装は型の定義モジュール内にのみ書ける。Intの定義はコアにあるため、コアのみが登録可能;ユーザは自分の型のみ登録可能。全型が平等、組み込み型に特権なし(2026-09-22) - [ ]
Indexに可変インデックス(Rust のIndexMut相当)を区別するか?(初回は読み取りのみ;可変インデックスは RFC-009 のWriteTokenに関与し、RFC-011a には既に&mut Selfレシーバの先例あり、後続に回す) - [ ] 複数位置インデックスの Key をタプル詰め合わせ(現状)か複数引数(Swift 流)か?(タプル詰め合わせ継続、パーサー改変なし)
- [ ]
Zero/Oneの形態:定数メンバーは演算子ではなく、「レシーバなしのインターフェースメンバー」は RFC-011a に先例なく、単独 RFC での確定後に RFC-011 のT: Add + Multiply + Zero全句が履行可能 - [ ]
Tryインターフェースの完全形状:?には成否判定、成功ペイロード抽出、失敗経路での外側関数値返却の三つが必要、単一メソッドresidualでは不十分;コンストラクタ parser 特例廃止と一括し、フェーズ 2 着工前に確定 - [ ]
?T前置型(RFC-026 FFI の Null 許容注釈、RFC-018)とe?後置演算子が?記号を共用:位置が異なる(型位置 vs 式位置)ため衝突しない、文面説明で十分 - [ ]
PartialOrd/Ordering(比較演算子のインターフェース化):独立 RFC、本 RFC では明確に扱わない
付録 A:調査証拠
以下の実測はすべて 0.8.0(target/debug/yaoxiang-rs.exe)上で再現。
A.1 インターフェース機構の可用性(本 RFC の地盤)
| 能力 | 実測 |
|---|---|
インターフェース宣言 Animal: (Self: Type) -> Type = {...} | ✅ 定義可能 |
インターフェースインスタンス化 + 外部メソッド Dog: { Animal(Dog) } + Dog.speak | ✅ 動作可能、正常出力(tests/rfc011a.rs に単体テストあり) |
| インターフェースインスタンス化 + 内部メソッド宣言 | ❌ E1097(フィールドとメソッドで名前空間衝突) |
メソッドディスパッチ d.speak() | ✅ 動作可能 |
注:E1097 は演算子メソッドが外部宣言形態(Point.add: (self: &Point, ...))でなければならないことを意味する、RFC-011a の例と一致。method_bindings の登録は environment.rs(add_method_binding)、照会は expressions.rs の呼び出し対象解析とフィールド検索失敗フォールバック経路。
A.2 演算子の現状
| 式 | 現状 |
|---|---|
1 + 2 / "a" + "b" / [1] + [2] | ✅ ハードコードホワイトリスト(Int/Float/String/List、両側同型を要求) |
1 + 2.5(Int + Float) | ❌ コンパイルエラー(ホワイトリストが両側同型を要求) |
-7 % 3 | -1(切り捨て余り;% ホワイトリストは Int/Float のみ、+ のホワイトリストとは異なる) |
Point(1,2) == Point(1,2) | ❌ E6007(Eq が Struct 上でランタイム失敗) |
(1,2) == (1,2) / [1] == [1] | ✅ ランタイム要素毎比較(Struct のみが穴) |
Any 位置 a == b(assert_eq) | ✅ ランタイム比較(RFC-036 実証) |
f[0](関数位置バインディング) | ⚠️ バインディング宣言内でのみ合法、式として使用すると E3006 |
arr[0, 1](複数位置) | ✅ タプル詰め合わせ、list([1, 2]) |
A.3 ? とコンストラクタのハードコード位置
// src/frontend/core/typecheck/inference/expressions.rs
let expected_result = MonoType::make_result(ok_ty.clone(), expected_err.clone());
// src/middle/core/ir_gen.rs
Instruction::VariantTag { group: "Result".to_string(), .. }
// variant 0 = ok, variant 1 = errコンストラクタ側:ok(T) / err(E) / some(T) はパーサーが認識(言語仕様 syntax.md §1.4.2); std/result.rs の is_ok / unwrap 等は variant_id 0/1 パターンで照合。「Result の std 帰属」は両半面(? + コンストラクタ)の同時解放が必要、いずれもフェーズ 2 に帰属。
付録 B:設計決定記録
| 決定 | 決定内容 | 理由 | 日付 |
|---|---|---|---|
| アーキテクチャ | 三層分離(マッピング表 / ディスパッチ基盤 / インターフェース契約) | 統合すると RFC-011 制約と演算子が切り離される | 2026-09-15 |
| 演算子通行条件 | インターフェース実装必須(Layer 2 が通行条件) | T: Add と + の厳密対応保証 | 2026-09-15 |
| インターフェース命名 | フルスペル(Mul ではなく Multiply) | RFC-011 既採用本体を改変しない | 2026-09-15 |
% インターフェース名 | Modulo(数学的剰余) | 名前でセマンティクスを固定 | 2026-09-15 |
| 比較演算子 | 初回はインターフェース化せず、ネイティブ IR 命令を保持 | 既に第一級命令;Ordering は一連の独立問題を派生 | 2026-09-15 |
Ordering | 初回導入せず | 実ニーズ駆動なし、独立 RFC の規模 | 2026-09-15 |
| 関連型 | インターフェース型パラメータで実現、type メンバー構文は導入しない | RFC-011a 既決定方案(Iterator: (Item: Type)) | 2026-09-15 |
| メソッドバインディングとインデックスの統一 | 概念統一、インターフェースはコンテナインデックスのみ担当 | 位置バインディングのキーはコンパイル時定数、結果型は型族評価が必要、ユーザ実装不可 | 2026-09-15 |
| 複数位置インデックス | タプル詰め合わせ + Key 型でオーバーロード、可変長インターフェースは導入しない | パーサー改変なし | 2026-09-15 |
| ビット演算 / 単項演算子 | 初回不実装 | カスタム型では稀、YAGNI | 2026-09-15 |
| 算術インターフェース形状 | 三つの型パラメータ (Self, R, O);T: Add ≜ Add(T, T, T) | 結果型明示化:1 + 2.5、スケールが表現可能;RFC-011 §8.3 昇格表と登録表が合一;Index(Key, Value) と形状統一 | 2026-09-22 |
Equal 派生 | デフォルト自動派生 + 明示的インスタンス化上書き;制約解決は同じ判定基準 | 「自分で書いた型を == できない」は不可;Tuple/List は既に要素毎比較、Struct のみが穴 | 2026-09-22 |
Equal 前置制約 | 「&mut 線形トークンを含まない」、Dup ではない | RFC-011 §2.4 でプリミティブが Dup に属さないことが明文化、Dup を前提とすると Point{Float,Float} を誤拒 | 2026-09-22 |
| 名前と登録分離 | 演算子はインターフェース実装登録表のみを照会、名前解決を経由しない | ローカル同名のバインディング(型レベル Add ファミリ等)と演算子は相互干渉なし;RFC-011 §5.2 例は改変不要 | 2026-09-22 |
| オーファンルール | 実装は型の定義モジュールに従う | 全型平等、組み込み型に特権なし | 2026-09-22 |
Any の == | ランタイム比較を保持、登録表を照会しない | RFC-036 assert_eq アサーション系が既依存 | 2026-09-22 |
Try 形状 | フェーズ 2 着工前に確定保留 | ? は三つのことを必要とし、単一メソッド residual では不十分;コンストラクタ parser 特例と一括 | 2026-09-22 |
% セマンティクス定性 | 欠陥修正(ドキュメントは既に乗除剰余と明記)、互換期間なし | reference/index.md「乗除剰余」を一次証言 | 2026-09-22 |
Result の std 帰属の根拠 | RFC-013 の既存位置付けを引用、存在しない番号は引用しない | RFC-013 に「std ライブラリ Result(T, Error)」既記載 | 2026-09-22 |
付録 C:用語集
| 用語 | 定義 |
|---|---|
| Layer 0 | 演算子 → メソッド名の固定マッピング表、言語レベル定数、ユーザ変更不可 |
| Layer 1 | Type.method キーで検索し呼び出すディスパッチ基盤 |
| Layer 2 | インターフェース契約層、generics 制約の根拠を提供し、同時に演算子の通行条件となる |
| インターフェース実装登録表 | コア既定登録とユーザインスタンス化を集約した実装総表;演算子照会と制約解決の唯一の判定基準、名前解決と無関係 |
| ネイティブファストパス | 基本型が保持するハードコード演算経路、インターフェースディスパッチを経由しない |
| 位置バインディング | RFC-004 の f[0] 構文、関数パラメータ位置をメソッドにバインド、コンパイル時挙動 |
| 自動派生 | コンパイラーが全フィールド比較可能なレコードに対し生成するフィールド毎 ==、明示的インスタンス化で上書き可能 |
参考文献
- RFC-011: ジェネリクスシステム設計 —
T: Add + Multiply + Zero制約、関連型 - RFC-011a: インターフェース実装と動的ディスパッチ — インターフェース宣言/インスタンス化/オーバーロード規則
- RFC-009: 所有権モデル設計 —
&mut T線形トークン - RFC-004: カリー化メソッドの複数位置結合バインディング —
f[0]構文 - RFC-010: 統一型構文 — 和型表現
- RFC-013: エラーコード仕様 —
Resultの std 帰属位置付け、エラーコードフロー - RFC-010b: パターンマッチングの完全化(バリアント分解と網羅性)
- Rust
std::ops::Index— 関連型Output設計 - Swift Subscripts — 複数パラメータ subscript
