Skip to content

Every object has a class of its own

Level: 301 · a Ruby programmer who has written def self.build and wants to know where that method went

One line: A method defined on one object — by def obj.greet, class << obj, define_singleton_method or extend — lives in that object's singleton class, an anonymous class slotted between the object and its real class; a class method is exactly this done to a class, which is why Foo.singleton_class prints as #<Class:Foo>, why Foo.new.make fails, and why dup loses these methods while clone keeps them.

Ruby's method lookup starts one step before the object's class. Every object (except immediates like 1 and :sym) can own a hidden class that holds methods for that object alone; its superclass is the real class, and lookup starts there. That is the whole trick behind class methods: def self.make inside class Foo defines make in Foo's singleton class, so Foo responds to it and instances of Foo do not. The program below builds one such object step by step and prints where each method ends up. An instance's singleton class has no name — its inspect shows the object's address — so the program prints comparisons and class names for it, and the real thing only for a class, whose singleton class is named after it.

Verified output of singleton_classes_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

1. a method defined on one object
   obj.greet                                      hi from obj
   other.greet                                    NoMethodError
2. where that method lives
   obj.singleton_methods                          [:greet]
   Foo.instance_methods(false).sort               [:inspect]
   obj.singleton_class.superclass == Foo          true
   obj.singleton_class.singleton_class?           true
   obj.class                                      Foo
   obj.instance_of?(Foo)                          true
3. class << obj opens the singleton class
   obj.name_length                                3
   obj.singleton_methods.sort                     [:greet, :name_length]
4. class methods are singleton methods of the class
   Foo.singleton_methods.sort                     [:build, :make]
   Foo.make                                       #<Foo made>
   Foo.build                                      built by Foo
   Foo.new("x").make                              NoMethodError
5. a class's singleton class has a name
   Foo.singleton_class                            #<Class:Foo>
   Foo.singleton_class.ancestors.first(3)         [#<Class:Foo>, #<Class:Object>, #<Class:BasicObject>]
   Foo.singleton_class.class                      Class
   Foo.singleton_class.superclass                 #<Class:Object>
   Class.class                                    Class
   Sub.singleton_class.superclass                 #<Class:Foo>
   Sub.make  (class methods are inherited)        #<Sub made>
6. define_singleton_method takes a block
   obj.wave                                       wave from obj
7. extend is include into the singleton class
   obj.singleton_class.include?(Loud)             true
   obj.singleton_class.ancestors (names)          ["(singleton)", "Loud", "Foo", "Object", "Kernel", "BasicObject"]
   obj.shout                                      LOUD from obj
   other.respond_to?(:shout)                      false
8. dup drops the singleton class, clone keeps it
   obj.dup.singleton_methods                      []
   obj.clone.singleton_methods.sort               [:greet, :name_length, :shout, :wave]
   clone.wave after renaming the clone            wave from clone
9. immediates have no singleton class
   1.singleton_class                              TypeError: can't define singleton
   :sym.singleton_class                           TypeError: can't define singleton
   nil.singleton_class                            NilClass

Reading the output

  • 1–2. def obj.greet gives one object a method; other, an instance of the same class, raises NoMethodError. The method is not in Foo (Foo.instance_methods(false) still lists only inspect) but in a class whose superclass is Foo and which answers singleton_class? with true. obj.class still says Foo and obj.instance_of?(Foo) is still true: the singleton class is invisible to class and instance_of?.
  • 3. class << obj … end opens that same class as a class body, so several methods can go in at once; singleton_methods lists both.
  • 4. def self.make and class << self; def build are the two spellings of the same thing, and Foo.singleton_methods lists both. A class method belongs to the class object alone: Foo.new("x").make is a NoMethodError, because instances look in Foo, not in Foo's singleton class.
  • 5. A class's singleton class prints as #<Class:Foo>, and its ancestors run in parallel to the class's own: #<Class:Foo> < #<Class:Object> < #<Class:BasicObject> < Class. That parallel chain is why a subclass inherits class methods: Sub.singleton_class.superclass is #<Class:Foo>, so Sub.make finds make there and builds a #<Sub made>. Foo.singleton_class.class is Class — a singleton class is an ordinary Class object that answers singleton_class? with true — and Class.class is Class, which closes the loop.
  • 6. define_singleton_method does what def obj.x does, from a block, so the name can be computed.
  • 7. extend(Loud) is include performed on the singleton class: Loud appears in the singleton class's ancestors right after the singleton class itself, and only obj gained shout.
  • 8. dup produces an object whose singleton_methods is empty; clone copies the singleton class, and the copied methods bind to the clone: after renaming the clone, wave reports the clone's name. dup and clone measures the rest of that difference.
  • 9. 1.singleton_class and :sym.singleton_class raise TypeError: can't define singleton — Integers and Symbols are immediates without a per-object class. nil.singleton_class is NilClass itself.

Compared with Python

Python has no singleton class, so its answers for the same nine rows are workarounds, and two of them are traps. Storing a function on an instance (obj.greet = greet) stores a plain function, not a method: calling it passes no self, which is the TypeError in row 1; types.MethodType(greet, obj) builds the bound method by hand. The closest thing to class << obj is to give the object a class of its own by assigning obj.__class__ to a freshly made subclass (row 3), and extend becomes the same move with the mixin first in the bases (row 7). Class methods differ in a way that matters: a @classmethod is reachable through an instance (Foo("x").make() works), while Ruby's class methods are not; the true analogue of a Ruby class method is a method on the metaclass, which Bar.forge() finds and Bar().forge() does not. Row 5 is the other half of the picture: the class of a class is type (or a metaclass), where Ruby has #<Class:Foo>; a subclass inherits the metaclass (type(BarSub) is Meta, so BarSub.forge() works) the way Sub inherits make; and type(type) is type closes the loop just as Class.class is Class. Row 8 is the second trap: copy.copy keeps both the per-object class and the instance dictionary, but the bound method it copied still points at the original object — dup.wave() reports "obj" after the copy was renamed, where Ruby's clone rebinds. Row 9: an int or a str instance takes no attributes, the way Ruby's immediates take no singleton class.

Verified output of singleton_classes_py.py — regenerated by tools/run_examples.py, never hand-typed.

1. a function stored on one object
   obj.greet = greet; obj.greet()                 TypeError (self is not passed)
   obj.greet = MethodType(greet, obj); obj.greet() hi from obj
   other.greet()                                  AttributeError
2. where that function lives
   sorted(vars(obj))                              ['greet', 'name']
   'greet' in vars(Foo)                           False
   type(obj) is Foo                               True
   type(obj).__name__                             Foo
   isinstance(obj, Foo)                           True
3. the closest to class << obj: a per-object subclass
   obj.name_length()                              3
   type(obj).__name__ / type(other).__name__      FooOnly / Foo
4. class methods live on the class or its metaclass
   Foo.make()                                     <Foo made>
   Foo.build()                                    built by Foo
   Foo('x').make()  (a classmethod is reachable)  <Foo made>
   Bar.forge()  (a metaclass method)              forged by Bar
   Bar().forge()                                  AttributeError
5. the class of a class is its metaclass
   type(Foo).__name__                             type
   [c.__name__ for c in type(Foo).__mro__]        ['type', 'object']
   type(type) is type                             True
   type(Bar).__name__                             Meta
   type(type).__name__                            type
   type(BarSub).__name__  (inherited metaclass)   Meta
   BarSub.forge()                                 forged by BarSub
6. setattr with a bound MethodType
   obj.wave()                                     wave from obj
7. extend: a per-object subclass with the mixin first
   isinstance(obj, Loud)                          True
   [c.__name__ for c in type(obj).__mro__]        ['FooLoud', 'Loud', 'FooOnly', 'Foo', 'object']
   obj.shout()                                    LOUD from obj
   hasattr(other, 'shout')                        False
8. copy.copy keeps the class and the bound methods
   type(copy.copy(obj)).__name__                  FooLoud
   sorted(vars(dup))                              ['greet', 'name', 'wave']
   dup.wave() after renaming the copy             wave from obj  (still bound to obj)
   dup.wave.__self__ is obj                       True
9. built-in instances take no attributes
   (1).greet = greet                              AttributeError
   "sym".greet = greet                            AttributeError
   type(None).__name__                            NoneType

Try it

  1. Build a counter with no class of its own: an Object.new given increment, count and inspect as singleton methods. Print its singleton_methods, show that a fresh Object.new does not respond to count, then dup and clone it: which copy keeps the @count instance variable, and which keeps the methods?
  2. Write module Greeter with a greet method and extend one String with it. Print the names of that string's singleton_class.ancestors (map each to name, or "(singleton)" when singleton_class? is true), then check "another".respond_to?(:greet). Finally extend the String class with the same module and explain what String.greet now does.
Solution to 1

singleton_classes_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.

# Exercise 1: an object with no class of its own. Build a counter from a bare
# Object.new using singleton methods only, then show what dup and clone keep.

counter = Object.new
counter.instance_variable_set(:@count, 0)

def counter.increment = @count += 1
def counter.count = @count
def counter.inspect = "#<counter #{@count}>"

3.times { counter.increment }
puts "counter                       #{counter.inspect}"
puts "counter.singleton_methods     #{counter.singleton_methods.sort.inspect}"
puts "counter.class                 #{counter.class}"
puts "Object.new.respond_to?(:count) #{Object.new.respond_to?(:count)}"

copy = counter.dup
twin = counter.clone
puts "dup keeps @count?             #{copy.instance_variable_get(:@count)}"
puts "dup.singleton_methods         #{copy.singleton_methods.inspect}"
puts "dup.respond_to?(:increment)   #{copy.respond_to?(:increment)}"
puts "clone.singleton_methods       #{twin.singleton_methods.sort.inspect}"
twin.increment
puts "clone after one increment     #{twin.inspect}"
puts "the original is untouched     #{counter.inspect}"

Verified output of singleton_classes_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

counter                       #<counter 3>
counter.singleton_methods     [:count, :increment, :inspect]
counter.class                 Object
Object.new.respond_to?(:count) false
dup keeps @count?             3
dup.singleton_methods         []
dup.respond_to?(:increment)   false
clone.singleton_methods       [:count, :increment, :inspect]
clone after one increment     #<counter 4>
the original is untouched     #<counter 3>

See also