Search code examples
rubyruby-1.9.3

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


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.


Solution

  • 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.