Blocks in Ruby
In Ruby, a block is a way to group statements together and pass them as arguments to methods. They are essentially anonymous functions that are associated with a method invocation. Blocks provide a flexible mechanism to define behavior that can be executed within a specific context. A fundamental and very unique aspect of Ruby is its blocks, which are sections of code that can be supplied to or coupled with method calls. An anonymous method is essentially what a Ruby code block is. It includes the necessary context for the Ruby code to run.
Syntax and Delimiters
Blocks are only considered legitimate when they immediately follow a method call; otherwise, they are not self-contained.
For defining blocks, Ruby offers two main syntaxes:
Curly Braces ({...}
): It is normally advised to use this format for brief, one-line chunks.
do...end
Keywords: When the block spans several lines, this format is usually utilized to improve readability.
Note on Precedence: The do…end
keywords have a lower precedence than the curly brace syntax ({}
), which binds more firmly. When curly braces are used in a method call without parenthesis, the block may inadvertently be bound to the final parameter rather than the method.
At the beginning of a block, there are vertical bars (|…|
) that define the parameters that the block can accept.
Code Examples (Syntax)
Single-line block using curly braces:
# The `times` method is an iterator defined on integers.
3.times { print "Ruby! " }
# Prints "Ruby! Ruby! Ruby! "
# Iterating over an array
[39-41].each { |i| puts i * i }
Output
Ruby! Ruby! Ruby! 4
Multi-line block using do...end
:
# The `upto` method is an iterator defined on integers.
1.upto(3) do |i|
if i % 2 == 0
puts "#{i} is even."
else
puts "#{i} is odd."
end
end
# 1 is odd.
# 2 is even.
# 3 is odd.
Output
1 is odd.
2 is even.
3 is odd.
Blocks and Iterators (Yield)
Similar to loops, Ruby’s sophisticated iterator methods (such as each, times, and map) are built on blocks.
A method uses the yield keyword to run an attached block. The yield statement gives the block temporary control back from the iterator method. after the yield statement is called, control moves to the block, and after the block is finished, the original method’s execution instantly resumes.
Values passed to the block from the yield statement are received via the block parameters specified in pipes (|…|
).
When calling yield, you can use the block_given?
method to see if a block was sent to a method. The outcome of calling yield without a block in between is a LocalJumpError
.
Examples of Code (Yield and Custom Methods)
Defining a method that uses yield:
def call_block
puts "Start of method"
yield # Passes control to the block
yield # Passes control again
puts "End of method"
end
call_block { puts "In the block" }
# produces:
# Start of method
# In the block
# In the block
# End of method
Output
Start of method
In the block
In the block
End of method
Passing arguments via yield
to a block:
def animals
yield "Tiger"
yield "Giraffe"
end
animals { |x| puts "Hello, #{x}" }
# Hello, Tiger
# Hello, Giraffe
Output
Hello, Tiger
Hello, Giraffe
Blocks as Closures and Proc Objects
Blocks and the idea of closures are closely related. Because it retains the variable bindings (context) that were in place when it was defined, a block is a closure. As a result, even if the block is performed later, when the local variables are technically out of scope, it can still access and use them.
Despite being syntactic structures, blocks “die once executed” since they are not objects in and of themselves and cannot be altered or stored for subsequent use.
The conversion of a block into a lambda or a Proc object is necessary in order to treat it as a first-class object. Kernel#lambda or Proc.new can be used explicitly, or the last parameter of a method definition can be prefixed with an ampersand (&) to accomplish this conversion implicitly.
Control Flow within Blocks
Usually, the result of the last expression evaluated inside a block is its return value. Within blocks, control flow keywords exhibit distinct behaviour:
next
: Causes the block to stop running in the current iteration and start running again in the iterator method (also known as yield). In C Language /Java, it is comparable to continuing. It is optional for the following statement to supply a value that serves as the block’s outcome for that iteration.
break
: Controls the statement that comes right after the iterator invocation, leaving the block and the iterator method that called it. It is optional for the break statement to supply a value that serves as the iterator invocation’s return value.
return
: The lexically enclosing method (the method the block is specified within in the source code) exits along with the block and the iterator when return occurs inside a block, unlike in nested functions in certain other languages.
Code Example ( in an Iterator)
(1..4).each do |item|
# Skip processing for even numbers
next if item.even?
puts "Item: #{item}"
end
Output
Item: 1
Item: 3
/ Blocks
Begin
/end
blocks are another control structure that Ruby employs to arrange several statements, apart from the code blocks linked to methods.
The value of the final statement run inside the begin block is returned by the begin block. When used in conjunction with exception handling clauses like rescue
, else
, and ensure
, or for conditional assignments (||=
), these blocks are especially helpful.
Important Distinction: It is not possible to send begin
blocks to functions; they are not the same as do…
end or {…
} code blocks. The begin keyword is frequently not required for exception handling when defining methods, modules, or classes because the block boundary is provided by the def
, module
, or class structure
.
Code Example (/)
puts begin
1
2
3
end
Output
3