In this series, I explore the differences between blocks, procs and lambdas starting with blocks.

What is a block?

A block is a chunk of code enclosed between a do and an end or a pair of curly braces ({}) as in the example below.

    { puts "hello" }

    # or

    do |number|
    	number * 2
    end

Like methods, blocks contain logic but they cannot be saved in a standalone variable. Instead, they are passed to methods and are called upon a method’s execution.

If you’ve used methods in Ruby’s Enumerable module before - think #map, #each, #select - then you will have encountered blocks already since we typically pass blocks to these generic functions.

Blocks can be called either implicitly or explicitly, let’s take a look at how:

Calling a block explicitly

The yield keyword enables you to implicitly call a block in Ruby. Let’s take a look at an example:

    def greeting
    	puts "salut"
    	yield
    	puts "ciao"
    end

    greeting { puts "hola" }

So, what’s happening here? During the execution of the #greeting method, the first string, ‘salut’, is printed to the console. Next, when the yield statement is reached, Ruby passes control from the method to the block. The block is invoked and ‘hola’ is printed to the console. We then jump back into the #greeting method and the method execution continues, printing ‘ciao’ to the console.

The output of the #greeting method is as follows:

    "salut"
    "hola"
    "ciao"
    => nil

Yield can be called multiple times in a single method call but you can only ever pass one block to a given method.

Calling a block explicitly

There is another, more explicit way of working with blocks in Ruby. This is by prefixing the last parameter in the method definition with an ampersand (&):

def greeting(&my_block)
puts "salut"
my_block.call
puts "ciao"
end

greeting { puts "hola" }

What’s happening here and why do we need an ampersand (&) in front of the my_block parameter?

The ampersand converts the block into a proc (by calling #to_proc on the block under the hood) and then assigns the result of this operation back to my_block. As we’re now dealing with a proc rather than a block, we use #call rather than yield in the method body to invoke it. We’ll take a closer look at procs in the next post in the series!

When explicitly calling a block, the block should be the last parameter passed to the method.

Error handling

What happens if a block is passed to a method but the method doesn’t use it? Well, the block is simply ignored. On the other hand, if you call yield inside a method but don’t pass a block to it, Ruby throws an error:

LocalJumpError (no block given (yield))

To avoid this, Ruby’s Kernel module provides a nifty method called #block_given? which checks if a block has been passed to the method and only invokes the block if there is one present:

def greeting
	yield if block_given?
	"hello"
end

greeting
=> "hello"

Passing arguments to blocks

You can also pass arguments to yield.

In the example below, when yield("Serena") is called invoked, the argument Serena is passed to the parameter |name| in the block.

    def greeting
    	puts "salut"
    	yield("Serena")
    	puts "ciao"
    	yield("reader")
    end

    greeting { |name| puts "*Hola #{name}*" }

    => 'salut'
    => '*Hola Serena*'
    => 'ciao'
    => '*Hola reader*'

It’s worth noting that the name parameter is local to the block, so it can’t be called inside the #greeting method.

To summarise, blocks can be considered as methods with no name. They are encapsulated chunks of code that, when invoked, can be injected into methods. In the next post, we’ll look at procs and lambdas and how these differ from blocks.