Closures in Rubinius

Now let’s take a look at how Rubinius handles blocks and closures. What happens inside of Rubinius when I call a block? Let’s use the same two examples we just did with JRuby - first a simple call to a block:

10.times do
  str = "The quick brown fox jumps over the lazy dog."
  puts str
end

Here’s how Rubinius handles this:

image

Since in Rubinius the Integer.times method is implemented in Ruby, the call to 10.times is a simple Ruby call. The Integer.times method, in turn, yields to my block code directly. All of this is implemented with Ruby!

Internally, Ruby compiles the 10.times do” call into these byte code instructions:

image

Here the text Rubinius::CompiledCode refers to the block I’m passing to the 10.times method call. Behind the scenes the Rubinius VM creates a C++ object to represent the block using the create_block instruction, and then passes that object along to the method call with the send_stack_with_block instruction.

Now let’s take another example and see how Rubinius handles closures:

str = "The quick brown fox"
10.times do
  str2 = "jumps over the lazy dog."
  puts "#{str} #{str2}"
end

Again, this time my block code refers to a variable defined in the parent scope. How does Rubinius implement this? To find out, let’s look at how Rubinius compiles the code inside the block into VM instructions:

image

I’ve shown the key VM instruction here in bold: Rubinius uses the push_local_depth instruction to walk up the stack and get the value of str from the parent scope. Internally Rubinius implements this instruction using a C++ object called VariableScope:

image

Just like the Java DynamicScope object did in JRuby, this C++ object saves an array of values - in other words the closure environment. Rubinius doesn’t use tricks with the VM stack to save pointers the same way that YARV does. There is no dynamic frame pointer; instead, Rubinius represents closure environments, blocks, lambdas, procs and bindings with a set of different C++ classes. I don’t have the space here to explain how all of that works in detail, but let’s take a quick look at how Rubinius uses the VariableScope object to obtain the str value from the parent scope:

image

This should look familiar - in fact, Rubinius functions in exactly the same way that JRuby does when accessing a parent scope. At a high level, the only difference is that VariableScope is written in C++ while JRuby’s DynamicScope object is written in Java. Of course, at a more detailed level the two implementations are very different.

Like JRuby, Rubinius stores the outer str variable in an instance of the VariableScope class and later creates a second VariableScope object when executing the code inside the block. The two VariableScope objects are connected with a parent pointer. When the Rubinius VM executes the push_local_depth instruction from inside the block, it follows the parent pointer up to obtain the value of str from the outer scope.

The most important and impressive feature of Rubinius is that it does implement many of the methods in Ruby’s core classes, such as Integer.times, in pure Ruby code. This means you can take a look right inside of Rubinius yourself to learn how something works. For example, here’s Rubinius’s implementation of the Integer.times method, taken from the kernel/common/integer.rb source code file:

def times
  return to_enum(:times) unless block_given?
  i = 0
  while i < self
    yield i
    i += 1
  end
  self
end

On the first line, Rubinius calls to_enum to return an enumerator object if a block is not given. But in the most common case when you do provide a block you want to iterate over, as in my 10.times example, Rubinius uses a simple Ruby while-loop that calls yield each time through the loop. This is exactly how you or I would probably implement the Integer.times method if Ruby didn’t provide it for us.