はじめに
Ruby オブジェクトモデルを理解する(.class / .superclass / ancestors) という記事で、Ruby のオブジェクトモデルを図で整理しました。
.class(俺を作ったのは?)
─────────────────────────→
BasicObject
↑ .superclass
Object
↑ .superclass
BasicObject ────────────.class───→ ┐ Module
↑ .superclass │ ↑ .superclass
Object ────────────.class───→ ├──→ Class ─┘
↑ .superclass │ ↑ │
"hello" ──.class──→ String ────────────.class───→ ┘ └─┘ .class(自分自身!)
↑ .superclass(俺の継承元は?)
-
縦(上向き) =
.superclassで継承元を辿る → クラスごとに違う -
横(右向き) =
.classで「俺を作ったクラス」を辿る → 全員 Class に行き着く
この図を眺めていると、ある矛盾に気づきます。
パラドックスの発見
Ruby のオブジェクトモデルには次の2つの事実があります。
BasicObject.class # => Class
Class.superclass # => Module
Module.superclass # => Object
Object.superclass # => BasicObject
つまり:
-
BasicObject.class = Class← Class が先に存在している必要がある -
Class.superclassを辿るとModule → Object → BasicObjectに行き着く ← BasicObject が先に存在している必要がある
図にすると:
BasicObject.class = Class ← Class が先に必要
↑
Class.superclass → BasicObject ← BasicObject が先に必要
どちらが先に存在できるのか?
これは有名な「鶏と卵」問題です。通常の Ruby コードではこの構造を作ることは絶対にできません。
# 通常の Ruby コードでは不可能
class BasicObject; end # ← この時点で BasicObject.class は何?
class Class < BasicObject; end # ← Class を定義するには BasicObject が必要
Ruby MRI がどう解決しているか
これは Ruby の C 実装(MRI)がブートストラップ時に2段階で強引に構築することで解決しています。
実際の C ソースコード(ruby/ruby/blob/master/class.c)を見てみましょう。
void
Init_class_hierarchy(void)
{
// ① まず superclass の繋がりだけ作る(.class の関係は後回し)
rb_cBasicObject = boot_defclass("BasicObject", 0);
rb_cObject = boot_defclass("Object", rb_cBasicObject);
rb_cModule = boot_defclass("Module", rb_cObject);
rb_cClass = boot_defclass("Class", rb_cModule);
// ② 後から .class を全員 Class に向ける
RBASIC_SET_CLASS(rb_cClass, rb_cClass);
RBASIC_SET_CLASS(rb_cModule, rb_cClass);
RBASIC_SET_CLASS(rb_cObject, rb_cClass);
RBASIC_SET_CLASS(rb_cBasicObject, rb_cClass);
}
ポイント1:boot_defclass とは
boot_defclass は通常の rb_define_class とは異なる特殊な関数です。
通常 Ruby でクラスを定義するには Class がすでに存在している必要があります。しかし boot_defclass は「.class への参照を空にしたまま」オブジェクトを作れるため、Class が存在しない状態でも使えます。
ポイント2:2段階で矛盾を回避
ステップ①: superclass の連鎖だけ構築
BasicObject(.class は空)
↑ superclass
Object(.class は空)
↑ superclass
Module(.class は空)
↑ superclass
Class(.class は空)
ステップ②: 全員の .class を Class に貼り付ける
BasicObject.class = Class ✅
Object.class = Class ✅
Module.class = Class ✅
Class.class = Class ✅(自己参照)
ステップ①では .class の関係をいったん無視して superclass の連鎖だけ作ります。この時点では矛盾が発生しません。
ステップ②で後から .class を全員 Class に向けることで、矛盾なく完成します。
まとめ
Ruby のオブジェクトモデルに見える「鶏と卵」パラドックスは、C ランタイムが2段階でブートストラップすることで解決されています。
| ステップ | 内容 |
|---|---|
| ① |
boot_defclass で superclass の連鎖だけ作る(.class は空のまま) |
| ② |
RBASIC_SET_CLASS で全員の .class を Class に向ける |
通常の Ruby コードでは絶対に書けない構造を、起動時に C のレベルで無理やり作っています。一言で言えば「Ruby のランタイムが最初に全部同時に作ったから矛盾しない」ということです。
参考