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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With