What the ampersand in front of &block means

by Jason Swett,

Here’s a code sample that I’ve grabbed more or less at random from the Rails codebase.

def form_for(record, options = {}, &block)

The first two arguments, record and options = {}, are straightforward to someone who’s familiar with Ruby. But the third argument, &block, is a little more mysterious. Why the leading ampersand?

This post will be the answer to that question. In order to begin to understand what the leading ampersand is all about, let’s talk about how blocks relate to Proc objects.

Blocks and Proc objects

Let’s talk about blocks and Proc objects a little bit, starting with Proc objects.

Here’s a method which takes an argument. The method doesn’t care of what type the argument is. All the method does is output the argument’s class.

After we define the method, we call the method and pass it a Proc object. (If you’re not too familiar with Proc objects, you may want to check out my other post, Understanding Ruby Proc objects.)

def proc_me(my_proc)
  puts my_proc.class
end

proc_me(Proc.new { puts "hi" })

If you run this code, the output will be:

Proc

Not too surprising. We’re passing a Proc object as an argument to the proc_me method. Naturally, it thinks that my_proc is a Proc object.

Now let’s add another method, block_me, which accepts a block.

def proc_me(my_proc)
  puts my_proc.class
end

def block_me(&my_block)
  puts my_block.class
end

proc_me(Proc.new { puts "hi" })
block_me { puts "hi" }

If we run this code the output will be:

Proc
Proc

Even though we’re passing a Proc object the first time and a block the second time, we see Proc for both lines.

The reason that the result of my_block.class is Proc is because a leading ampersand converts a block to a Proc object.

Before moving on I encourage you to try out the above code in a console. Poke around at the code and change some things to see if it enhances your understanding of what’s happening.

Converting the Proc object to a block before passing the Proc object

Here’s a slightly altered version of the above example. Notice how my_proc has changed to &my_proc. The other change is that Proc.new has changed to &Proc.new.

def proc_me(&my_proc) # an & was added here
  puts my_proc.class
end

def block_me(&my_block)
  puts my_block.class
end

proc_me(&Proc.new { puts "hi" }) # an & was added here
block_me { puts "hi" }

If we run this code the output is the exact same.

Proc
Proc

This is because not only does a leading ampersand convert a block to a Proc object, but a leading ampersand also converts a Proc object to a block.

When we do &Proc.new, the leading ampersand converts the Proc object to a block. Then the leading ampersand in def proc_me(&my_proc) converts the block back to a Proc object.

I again encourage you to run this code example for yourself in order to more clearly understand what’s happening.

The differences between blocks and Proc objects

Ruby has a class called Proc but no class called Block. Because there’s no class called Block, nothing can be an instance of a Block. The material that Ruby blocks are made out of is Proc objects.

What happens when we try this?

my_block = { puts "hi" }

If we try to run this, we get:

$ ruby block.rb
block.rb:1: syntax error, unexpected string literal, expecting `do' or '{' or '('
my_block = { puts "hi" }
block.rb:1: syntax error, unexpected '}', expecting end-of-input
my_block = { puts "hi" }

That’s because the syntax { puts "hi" } doesn’t make any syntactical sense on its own. If we want to say { puts "hi" }, there are only two ways we can do it.

First way: put it inside a Proc object

That would look like this:

Proc.new { puts "hi" }

In this way the { puts "hi" } behavior is “packaged up” into an entity that we can then do whatever we want with. (Again, see my other post on Ruby proc objects for more details.)

Second way: use it to call a method that takes a block

That would look like this:

some_method { puts "hi" }

Why converting a block to a Proc object is necessary

Let’s take another look at our code sample from the beginning of the post.

def form_for(record, options = {}, &block)

In methods that take a block, the syntax is pretty much always &block, never just block. And as we’ve discussed, the leading ampersand converts the block into a Proc object. But why does the block get converted to a Proc object?

Since everything in Ruby is an instance of some object, and since there’s no such thing as a Ruby Block class, there can never be an object that’s a block. In order to be able to have an instance of something that represents the behavior of a block, that thing has to take the form of a Proc object, i.e. an instance of the class Proc, the stuff that Ruby blocks are made out of. That’s why methods that explicitly deal with blocks convert those blocks to Proc objects first.

Takeaways

  • A leading ampersand converts a block to a Proc object and a Proc object to a block.
  • There’s no such thing as a Ruby Block class. Therefore no object can be an instance of a block. The material that Ruby blocks are made out of is Proc objects.
  • The previous points taken together are why Ruby block arguments always appear as e.g. &block. The block can’t be captured in a variable unless it’s first converted to a Proc object.

5 thoughts on “What the ampersand in front of &block means

  1. David A. Black

    Another fun thing about the & is that it generally calls #to_proc on whatever follows it. So something like this:

    [‘one’, ‘two’, ‘three’].map(&:upcase)

    is basically:

    [‘one’, ‘two’, ‘three’].map {|str| str.send(:upcase) }

    because symbols have a built-in #to_proc method in which they send themselves to the proc’s argument. (Of course, map(:upcase.to_proc) (with no &) won’t work as the & has to be there to signal to use the resulting proc as a block and not an argument. (&:upcase.to_proc) will work because calling #to_proc on a proc just returns self.)

    This can be taken to extremes (just for illustration) — for example:

    obj = Object.new
    def obj.to_proc; Proc.new { print ‘hi!’ }; end
    [1,2,3].each(&obj) # => ‘hi!hi!hi!’

    I’m not sure I’ve ever seen anything like that in real life but it’s cool that it works đŸ™‚

    Reply
  2. Dana Kashubeck

    Thanks for this! I always kinda understood what was happening there, but never dug into the specifics. Once I understood that you can’t have a block be assigned to a variable, `&block` became a head-slapper.

    Reply
  3. Jeremy

    I’m not sure the article gets to the nub of the issue for me. You talk about there being no Block object, but I think it’s more accurate to say there’s not really such a thing as a block. When you pass a code block to a method directly that is a proc. But the obvious issue is when you want to pass an existing proc in, that special “block” thing that takes an inline proc isn’t a parameter on the method. The & is just telling ruby “set this thing as the proc of the method call” when you make the call, and the & in the param list says “set this variable to the proc of the method call”

    It gets interesting when you grab that proc in the method and store it for later use. That’s how you can build interesting DSLs

    Reply
  4. Ben

    This stuff does my head in. plus lambdas and arities. in addition to the closure complications / differences between them all.

    i can’t help but think it’s needlessly complicated.

    Reply
  5. VĂ­tor

    > The material that Ruby blocks are made out of is Proc objects.

    I’ve seen you state this in another article of yours, but shouldn’t it be the other way around?

    According to the Ruby documentation (https://ruby-doc.org/core-3.1.2/Proc.html), “A Proc object is an encapsulation of a block of code […]”. From what I understand, this means that Proc objects basically wraps a block of code.

    It makes perfect sense to me that a Proc is an abstraction/wrapper class. But, I don’t see how **Ruby blocks being made out of encapsulated blocks** makes sense.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *