Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby metaclasses: why three when defined singleton methods?

Tags:

ruby

Let's count classes in MRI scope:

def count_classes
  ObjectSpace.count_objects[:T_CLASS]
end
k = count_classes

Define class with class method:

class A
  def self.foo
    nil
  end
end

And run:

puts count_classes - k
#=> 3

Please, explain me, why three?

like image 814
isqad Avatar asked Nov 08 '13 16:11

isqad


2 Answers

Looking at MRI code, every time you create a Class which in Ruby is object of type Class, automatically, ruby creates "metaclass" class for that new class, which is another Class object of singleton type.

The C function calls (class.c) are:

rb_define_class
  rb_define_class_id
    rb_class_new(super);
    rb_make_metaclass(klass, RBASIC(super)->klass);

So, every time that you define a new class, Ruby will define another class with meta information.

When you define a class method, I mean, def self.method, internally, ruby calls rb_define_singleton_method. You can check it doing the follow step:

Create a ruby file test.rb:

class A
  def self.foo
  end
end

And run the following command:

ruby --dump insns test.rb

You will have the following output:

== disasm: <RubyVM::InstructionSequence:<main>@kcount.rb>===============
0000 trace            1                                               (  70)
0002 putspecialobject 3
0004 putnil
0005 defineclass      :A, <class:A>, 0
0009 leave
== disasm: <RubyVM::InstructionSequence:<class:A>@kcount.rb>============
0000 trace            2                                               (  70)
0002 trace            1                                               (  71)
0004 putspecialobject 1
0006 putself
0007 putobject        :foo
0009 putiseq          foo
0011 opt_send_simple  <callinfo!mid:core#define_singleton_method, argc:3, ARGS_SKIP>
0013 trace            4                                               (  73)
0015 leave                                                            (  71)
== disasm: <RubyVM::InstructionSequence:[email protected]>==================
0000 trace            8                                               (  71)
0002 putnil
0003 trace            16                                              (  72)
0005 leave

The define_singleton_method is mapped to the rb_obj_define_method C function (object.c), which do following calls:

 rb_obj_define_method
   rb_singleton_class(obj)
   rb_mod_define_method

The function rb_singleton_class exposes the metaclass created when the class was defined, but it also creates a new metaclass for this metaclass.

According the Ruby documentation for this function: "if a obj is a class, the returned singleton class also has its own singleton class in order to keep consistency of the inheritance structure of metaclasses".

That is the reason why the number of class increases by 1 when you define a class method.

The same effect happens if you change your code by:

class A
end
A.singleton_class

The singleton_class is mapped to rb_obj_singleton_class C function, which calls the rb_singleton_class.

Even if you create a class method and call the singleton_class method, the number of created classes will not change, because all classes necessary to handle meta information is already created. Example:

class A
  def self.foo
    nil
  end
end

A.singleton_class

The code above will keep returning 3.

like image 177
Thiago Lewin Avatar answered Oct 12 '22 19:10

Thiago Lewin


The first one is the class' eigenclass. The second one is related to the eigenclass as well, as a method handler:

>> def count_classes
>>   ObjectSpace.count_objects[:T_CLASS]
>> end
=> nil
>> k = count_classes
=> 890
>> class A; end
=> nil
>> puts count_classes - k
2                                         # eigenclass created here
=> nil
>> k = count_classes
=> 892
>> class A; def self.foo; nil; end; end
=> nil
>> puts count_classes - k
1                                         # A/class eigenclass method handler?
=> nil
>> k = count_classes
=> 893
>> class A; def bar; nil; end; end
=> nil
>> puts count_classes - k
0                                         # instance method don't count
=> nil
>> class A; def self.baz; nil; end; end
=> nil
>> puts count_classes - k
0                                         # A/eigenclass already has a handler
=> nil
>> class B < A; end
=> nil
>> puts count_classes - k
2                                         # the class and its eigenclass, again
=> nil
>> class C; end
=> nil
>> k = count_classes
=> 897
>> class C; def foo; end; end
=> nil
>> puts count_classes - k
0                                         # so... definitely class method related
>> class B; def self.xyz; end; end
=> nil
>> puts count_classes - k
1                                         # B/eigenclass handler
=> nil
>> k = count_classes
=> 898
>> a = A.new
=> #<A:0x007f810c112350>
>> puts count_classes - k
0
=> nil
>> def a.zyx; end
=> nil
>> puts count_classes - k
1                                         # a/eigenclass handler
=> nil

I'm not familiar enough with ruby internals to know for sure, but that would be my best guess.

like image 35
Denis de Bernardy Avatar answered Oct 12 '22 19:10

Denis de Bernardy