Reputation: 290
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?
Upvotes: 5
Views: 293
Reputation: 12578
At the ObjectSpace
doc page, note the sentence: "The contents of the returned hash are implementation specific. It may be changed in future." In other words, with ObjectSpace.count_objects
you never know, unless you dig deep in the particular Ruby implementation. Let me demonstrate this for you:
def sense_changes prev
ObjectSpace.count_objects.merge( prev ) { |_, a, b| a - b }.delete_if { |_, v| v == 0 }
end
prev = ObjectSpace.count_objects
# we do absolutely nothing
sense_changes( prev )
#=> { :FREE=>-364,
:T_OBJECT=>8,
:T_STRING=>270,
:T_HASH=>11,
:T_DATA=>4,
:T_MATCH=>11,
:T_NODE=>14}
And you can keep wondering until the cows come home what the heaven has happened in the ObjectSpace
while you have done nothing. As for the :T_CLASS
field change by 3, that you observed, Denis's answer applies: 1 is caused by the class itself, 1 by its eigenclass, and 1 by we don't know what (Update: As tlewin has shown, it's the eigenclass's eigenclass). Let me just add that Ruby objects do not have allocated eigenclasses upon creation (Update: As tlewin has shown, classes are an exception to this rule.).
Unless you are a core developer, you barely need to wonder about the contents of ObjectSpace.count_objects
hash. If you are interested in accessing classes via ObjectSpace
, use
ObjectSpace.each_object( Class )
Indeed:
k = ObjectSpace.each_object( Class ).to_a
a = Class.new
ObjectSpace.each_object( Class ).to_a.size - k.size
#=> 1
ObjectSpace.each_object( Class ).to_a - k == [ a ]
#=> true
Upvotes: 2
Reputation: 2828
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.
Upvotes: 4
Reputation: 78463
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.
Upvotes: 2