Why does ruby create 3 objects after a class is created?

376 Views Asked by At

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.

1

There are 1 best solutions below

6
Ana María Martínez Gómez On BEST ANSWER

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.