I was studying about Ruby's metaclass. I read this answer where it is nicely described what metaclass is. It's showed there when a class is created it will create two objects. Which is understandable. One for the class itself and one for it's metaclass. But when I am trying it myself I see that it is creating three objects.
puts "Before Class Creation object count - #{ObjectSpace.count_objects[:T_CLASS]}"
class Test
def self.foo # test_singleton
p 'Printed from method #foo'
end
def bar # test
p 'Printed from method #bar'
end
end
puts "After Class Creation object count - #{ObjectSpace.count_objects[:T_CLASS]}"
###############
Before Class Creation object count - 949
After Class Creation object count - 952
I am using Ruby - 2.5.1
.
Can anyone help me understand this one?
Update:
The reference SO post that I added is using ruby-1.9.1 or greater, as the method count_objects
for ObjectSpace
was introduced in 1.9.1. It seems that the T_CLASS
count has always always been 3 (tried with ruby-1.9.3-p551
).
So, till now it's still a mystery why this answer. Ruby under a microscope also says the count is 2.
From https://bugs.ruby-lang.org/issues/16788:
Creating a class automatically creates a singleton class (which is not accessible to the user). Referencing the singleton class of a class automatically creates a singleton class of that singleton class. This is to keep consistency of the inheritance structure of metaclasses. Otherwise, class methods wouldn't inherit from the superclass's metaclass, which is necessary as the superclass's class methods should be available as the subclass's class methods.
Modifying the question code a bit:
$old_classes = []
def print_objects
new_classes = []
ObjectSpace.each_object(Class){|x| new_classes << x}
puts "New classes: #{new_classes - $old_classes}" unless $old_classes.empty?
puts "Counts: #{ ObjectSpace.count_objects[:T_CLASS] }"
$old_classes = new_classes
end
print_objects
class Test
end
puts 'Test class created'
print_objects
class Test
def self.foo
end
end
puts 'Test singleton class referenced'
print_objects
I get the following results:
Counts: 690
Test class created
New classes: [Test]
Counts: 692
Test singleton class referenced
New classes: [#<Class:Test>]
Counts: 693
I tried it with Ruby 2.6 and 2.0 both inside and outside a console (the numbers differ but the difference is the same) and @SajibHassan with 1.9.3 (version in which the method count_objects
was introduced).
This means that the difference has always been 3 and that the first singleton class created is not accessible for the user.
The book Ruby Under a Microscope (written in 2012 after the release of Ruby 2.1) also describes the creation of only two metaclasses, which doesn't match the result we get.
Note that methods like Module#prepend
(introduced in Ruby 2.0), which was mentioned by @JörgWMittag in the comments as the possible reason for this extra class, use T_ICLASS
. Check the commit in which the method was introduced for details. I guess that T_ICLASS
stands for internal class and consequently internal classes shouldn't be visible to the user (which makes sense). I am not sure though why some T_CLASS
are accessible to the user and some others are not.