Recently, one of the intern at my company asked me how can we pass a symbol as a block parameter to a method call in Ruby. Let’s take a look at how it is implemented in Ruby.

First thing first, a small script to help us decompile Ruby code to Ruby’s bytecode instructions.

To use this script, we need to provide the file name of a Ruby script. For example:

$> echo puts "Disasm this" > test.rb$> disasm test.rb
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (  14)
0002 putself
0003 putstring        "Disasm this"
0005 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0007 leave

having enough tool, let’s dig into the code.

Our concern is the following code:

Let take a look at the compiled bytecode

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (   1)
0002 duparray         [1, 2, -3]
0004 putobject        :abs
0006 send             <callinfo!mid:map, argc:0, ARGS_BLOCKARG>
0008 leave

for anyone who is not familiar with these bytecodes, the above code will:

• push [1,2,-3] to stack(as a receiver of method map)
• push symbol :abs to stack(as a block argument)
• call send instruction

One interesting thing here is the flag ARGS_BLOCKARG, which hold true when passing &:abs to map.

Let’s take a look at how send instruction is implemented in Ruby(it is defined in insns.def of Ruby’s source code)

We can see that when ARGS_BLOCKARG flag is set(which mean we are passing a block), ci->flag will be set to VM_CALL_ARGS_BLOCKARG

Let’s find the definition of vm_caller_setup_arg_block(it is defined in vm_args.c)

Wow, lots of stuff happens here, but what draw my attention is the condition ci->flag & VM_CALL_ARGS_BLOCKARG which is true in our case.

In the middle of the if statements, we can see a condition to check if the passing block is a Proc, if it is not, a calling to function rb_check_convert_type(proc, T_DATA, "Proc", "to_proc") will be used.

If the passing block is not a Proc, Symbol#to_proc will be used to convert out symbol to Proc before going on.

So, underneath, Ruby will convert my symbol to a Proc and passing this as a block parameter to map.

## Symbol#to_proc

This method will return a Proc object which will response to the given method by symbol.

For example

Therefore when we declare such as [1,2,-3].map(&:abs), :abs will be converted to a Proc and then passing each of the element as a parameter to this Proc