What is the Ruby Top-Level?

The Ruby top-level is an interesting place, behaving at times like a class (the Object class) and other times like an object (a plain instance of Object). It is where we define most of our classes and where we define so-called global functions.

Top-level as an Object

First, let’s verify that top-level is an object:

puts self #=> main
puts self.class #=> Object
puts self.kind_of?(Module) #=> false

So, top-level is a direct instance of the Object class (it is not a Module or a Class) and invoking inspect on it returns main. It also has its own instance variables:

@x = :hello
instance_variables #=> [:@x]
Object.instance_variables #=> []

And, because it is just a plain object and not a Module, it cannot access certain methods:

puts self.ancestors #=> NoMethodError

In summary, the top-level appears at first blush to be a run-of-the-mill Object instance created by a simple Object.new. It is not a class nor a module and so doesn’t inherit methods such as ancestors. It also has its own instance variables that are separate and distinct from the instance variables on the Object class.

Top-level as a Class

Sometimes though, top-level behaves like the Object class itself: Methods defined at top-level become private instance methods on the Object class:

def hello
  puts "hello"
end

method(:hello).owner #=> Object
Object.private_method_defined?(:hello) #=> true

This is how Ruby implements ‘global functions': since top-level methods are defined on Object they are accessible everywhere, and as they’re private they must be invoked in a functional style (with no explicit receiver).

Similarly, constants defined at top-level become constants defined under Object:

Const = :hello

Object.const_defined?(:Const, false) #=> true

So it seems that when it comes to defining methods and constants, top-level no longer behaves like a simple object and instead acts like the Object class itself.

Top-level as a strange class/object hybrid

The above class-like behaviour of top-level can be explained by the deep-magic of altering the default definee and cref contexts. But top-level also supports some method calls that have no place in the method table of a lowly object:

module M
  def hello
    :hello
  end
end

# wtf?
include M

# wtf?
public
def goodbye
  :goodbye
end

These methods behave as if the receiver is the Object class (include brings the module into Object) but are nonetheless invoked on main. Where do these methods come from?

method(:include).owner #=> #<Class:#<Object:0xb0f0b8>>
method(:public).owner #=> #<Class:#<Object:0xb0f0b8>>

They’re defined on the singleton class of main. Let’s see what other methods are defined there:

puts singleton_class.instance_methods(false).inspect
#=> [:to_s, :public, :private, :include]

Note that these methods (with the exception of to_s) are highly unusual as they act on an object other than their receiver.

This behaviour is a combination of class-like and object-like behaviour. The method itself is defined on the singleton class of the main object yet it is acting on the Object class. Nonetheless these are useful methods to have at top-level and their behaviour more or less corresponds to what you’d expect.

Pulling back the veil

Let’s remove the to_s method from the singleton class of main:

self.to_s #=> "main"

class << self
  remove_method :to_s
end

self #=> #<Object:0xb0f0b8>

As expected, the enigmatic main is just the return value of its singleton’s to_s method.

As main is just an object (albeit an unusual one) we can pass it around to methods, and make use of some of its curious properties (its singleton methods):

def weird(obj)
  obj.include Module.new { def hello; :hello; end }
end

weird(self)

hello #=> :hello

From above, we can invoke the include method on main even when it’s passed as a parameter to a method — but this should not be surprising. The module is then mixed into the `Object` class as per its behaviour with main as implied receiver.

Conclusion

Top-level has a dual-nature. In some circumstances (i.e the getting and setting of ivars and method lookup) it behaves as an ordinary Object instance. In other circumstances (i.e the defining of methods and constants) it acts as if it was the Object class itself. And in still other situations it behaves like a kind of combination of the two (i.e include, public, and private).

Eerie.

About these ads
Tags: , ,

7 Responses to “What is the Ruby Top-Level?”

  1. ruby is.. weird =D

  2. I have been trying to convince Matz that the top-level should be a self-extended module that does not effect Object for a while now. It would be nice if others got on the band-wagon.

  3. This is cute and awesome, and i wish i dynamically link it to my opensource firmware. thanks banisiter

  4. This looks really interesting Mr Bannister. Tell me, if an object doesn’t have a method does that make it a class? Or vice versa, if you give an class a method, does it become an object?

Trackbacks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: