object.c

Rubyの勉強のために始めたブログのはずが、Rubyが置き去り。思い出したように少し何かをと思って、object.cを眺めてみた。すべてのオブジェクトの親ったって何ができるわけでもないし、見てもつまらないと思って、array.cとかstring.cがおもしろいと思ってたけど、なんのなんの、やっぱりキモかなという気がしてきた。

オブジェクトの等値性とか、代入とかコピー、クローニングといったことは自明ではなくて、結構ややこしいものというのがぼくの理解だけど、Cレベルで見ればすっきり理解できそうな気がしている。要するにポインタの話なのか、オブジェクトのメモリイメージのデータ部なのか、メタ情報も含むすべてなのかとか、そんな話かな、みたいな。JavaとかRubyにはポインタがないという話だけど、実はポインタを先に説明したほうが人間には理解しやすいんじゃないかと思ったりもしている。

Object#dupは、以下の通り。

VALUE
rb_obj_dup(VALUE obj) {

    VALUE dup;

    if (rb_special_const_p(obj)) {
        rb_raise(rb_eTypeError, "can't dup %s", rb_obj_classname(obj));
    }
    dup = rb_obj_alloc(rb_obj_class(obj));
    init_copy(dup, obj);

    return dup;
}

(object.c)

rb_obj_allocがオブジェクト生成のキモかしら。中身のないVALUEのポインタを返してるっぽい。

VALUE
rb_obj_alloc(VALUE klass)
{
    VALUE obj;

    if (RCLASS_SUPER(klass) == 0 && klass != rb_cBasicObject) {
        rb_raise(rb_eTypeError, "can't instantiate uninitialized class");
    }
    if (FL_TEST(klass, FL_SINGLETON)) {
        rb_raise(rb_eTypeError, "can't create instance of singleton class");
    }
    obj = rb_funcall(klass, ID_ALLOCATOR, 0, 0);
    if (rb_obj_class(obj) != rb_class_real(klass)) {
        rb_raise(rb_eTypeError, "wrong instance allocation");
    }
    return obj;
}

なるほど。クラスオブジェクト(の参照)を引数に渡して、オブジェクトのID だけ作っている。中身についてはdupのほうで、init_copy(dup, obj)でコピーしている。

ちょっと気になったのはdupの中にあるrb_special_const_p関数。これはruby.hで定義があって、

static inline int
{
    if (SPECIAL_CONST_P(obj)) return Qtrue;
    return Qfalse;
}

:

#define SPECIAL_CONST_P(x) (IMMEDIATE_P(x) || !RTEST(x))

(ruby.h)

となっている。VALUEは、すべてのオブジェクトへのポインタが入っているけど、小さな整数なんかは即値で入っていてdupが使えない。え、まじかと思ってやったらほんとにそうだった。

>> "1".dup
=> "1"
>> 1.dup
TypeError: can't dup Fixnum
        from (irb):58:in `dup'
        from (irb):58
        from /usr/local/bin/irb:12:in `<main>'
>>

なるほど。

Object#cloneのほうは、

VALUE
rb_obj_clone(VALUE obj)
{
    VALUE clone;

    if (rb_special_const_p(obj)) {
        rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj));
    }
    clone = rb_obj_alloc(rb_obj_class(obj));
    RBASIC(clone)->klass = rb_singleton_class_clone(obj);
    RBASIC(clone)->flags = (RBASIC(obj)->flags | FL_TEST(clone, FL_TAINT) | FL_TEST(clone, FL_UNTRUSTED)) & ~(FL_FREEZE|FL_FINALIZE);
    init_copy(clone, obj);
    RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE;

    return clone;
}

となっていて、fronzen/trusted/taintedなんかの情報もコピーしている。

それにしても、もろもろうまくできてるなと思う。名前の付け方といい、処理の順番といい、ものすごく複雑なことをやってるように思うのだけど、どうやったらこうも整理できるんだろうかという印象を受ける。概念の整理という意味でも、実装上の整理という意味でも。で、rb_とか、接頭辞を見ていると、C++ みたいな名前空間は別にいらんのじゃないかという気もしてくる。

とか思いながら、あちこちを拾い読み。ぼくは読書百遍を信じてる人なので、よく分からないままでも気にせず読んでいれば、だんだん分かることも増えるだろうと思っている。実際、Ruby命名規則や基本的な主要なマクロの役割は、だいぶ把握できてきたような気がする。言語処理系のキモの部分はまったく読んでないけど、外堀からまったりと。次はmixinで何でincludeされたmoduleのメソッドがちゃんと呼べるのかとか、メソッドのルックアップ関連の仕組みが知りたいと思っている。