Overview
Ruby treats blocks as first-class objects that methods can receive, execute, and manipulate. Block parameters represent the arguments passed into blocks when methods yield control, while return values encompass the data that flows back from method execution, block evaluation, and yield operations.
Every Ruby method can accept a block, whether explicitly declared or not. When a method calls yield
, Ruby executes the associated block and passes arguments as block parameters. The block can access these parameters through the parameter list defined between pipe characters |param1, param2|
.
def process_data(array)
array.each { |item| yield(item, item.length) }
end
process_data(['ruby', 'python']) { |lang, size| puts "#{lang}: #{size}" }
# ruby: 4
# python: 6
Return values flow in multiple directions. Methods return values to their callers, blocks return values to the methods that execute them, and yield
expressions return the result of block execution back to the yielding method.
def transform_and_sum(numbers)
total = 0
numbers.each do |num|
result = yield(num) # yield returns block's return value
total += result
end
total # method's return value
end
sum = transform_and_sum([1, 2, 3]) { |n| n * 2 } # => 12
Ruby provides multiple ways to work with blocks beyond basic yielding. The block_given?
method checks for block presence, Proc.new
captures blocks as objects, and the &block
parameter syntax converts blocks to Proc
instances. Each approach affects how parameters pass into blocks and how return values flow back.
Basic Usage
Methods yield values to blocks by calling yield
with arguments. The block receives these arguments as parameters defined in its parameter list. Block parameters follow standard variable scoping rules within the block's execution context.
def calculate_with_index(items)
items.each_with_index do |item, index|
yield(item, index, item * index)
end
end
calculate_with_index([5, 10, 15]) do |value, pos, product|
puts "Position #{pos}: #{value} * #{pos} = #{product}"
end
# Position 0: 5 * 0 = 0
# Position 1: 10 * 1 = 10
# Position 2: 15 * 2 = 30
Block parameter counts do not need to match the number of yielded arguments. Ruby assigns nil
to missing parameters and ignores extra yielded values. This flexibility allows methods to yield varying argument counts without breaking calling code.
def flexible_yield
yield(1)
yield(2, 'extra')
yield(3, 'more', 'data')
end
flexible_yield { |a, b| puts "a=#{a}, b=#{b}" }
# a=1, b=
# a=2, b=extra
# a=3, b=more
The yield
expression returns the value of the block's last evaluated expression. Methods can capture and use this return value for further processing, accumulation, or conditional logic.
def collect_results(items)
results = []
items.each do |item|
processed = yield(item)
results << processed if processed
end
results
end
numbers = collect_results([1, 2, 3, 4, 5]) do |n|
n.even? ? n * 2 : nil
end
# => [4, 8]
Block parameters can include default values, splat operators, and keyword arguments, similar to method parameters. The double splat **
captures keyword arguments passed through yield
.
def advanced_yield
yield(1, 2, extra: 'data', flag: true)
end
advanced_yield do |a, b=0, *rest, **kwargs|
puts "a=#{a}, b=#{b}, kwargs=#{kwargs}"
end
# a=1, b=2, kwargs={:extra=>"data", :flag=>true}
Advanced Usage
Ruby converts blocks to Proc
objects using the &
operator in method parameter lists. This conversion allows methods to store, pass, and manipulate blocks as first-class objects while maintaining access to block parameters and return values.
def store_and_call(data, &block)
stored_proc = block
data.map do |item|
result = stored_proc.call(item)
[item, result]
end
end
pairs = store_and_call([1, 2, 3]) { |n| n ** 2 }
# => [[1, 1], [2, 4], [3, 9]]
Methods can accept multiple Proc
objects as regular parameters and execute them with different argument sets. Each Proc
maintains its own parameter signature and return value handling.
def dual_processing(items, transformer, filter)
results = []
items.each do |item|
transformed = transformer.call(item)
if filter.call(transformed)
results << transformed
end
end
results
end
numbers = [1, 2, 3, 4, 5]
square = ->(n) { n * n }
even_filter = ->(n) { n.even? }
filtered = dual_processing(numbers, square, even_filter)
# => [4, 16]
Block parameters can destructure array and hash arguments automatically. When yielding arrays, Ruby unpacks them into separate block parameters. Hash destructuring requires explicit parameter syntax.
def yield_pairs
[['a', 1], ['b', 2], ['c', 3]].each { |pair| yield(pair) }
end
# Array destructuring
yield_pairs { |(key, value)| puts "#{key} => #{value}" }
# a => 1
# b => 2
# c => 3
def yield_hashes
[{name: 'Ruby', year: 1995}, {name: 'Python', year: 1991}].each { |h| yield(h) }
end
# Hash parameter destructuring
yield_hashes { |name:, year:| puts "#{name} (#{year})" }
# Ruby (1995)
# Python (1991)
The return
statement inside blocks affects the enclosing method, not just the block. This behavior can cause unexpected control flow when blocks contain explicit return
statements.
def method_with_block_return
[1, 2, 3].each do |n|
return "Found #{n}" if n == 2 # returns from method_with_block_return
end
"Not found"
end
result = method_with_block_return # => "Found 2"
Blocks can capture and modify variables from their enclosing scope through closures. The captured variables remain accessible even after the original scope ends, maintaining their modified values.
def create_accumulator(start = 0)
total = start
->(value) do
total += value
total
end
end
acc = create_accumulator(10)
puts acc.call(5) # => 15
puts acc.call(3) # => 18
puts acc.call(2) # => 20
Common Pitfalls
Block parameter shadowing occurs when block parameters use the same names as variables in the enclosing scope. Ruby creates new local variables within the block, preventing access to the outer variables.
def shadowing_example
value = "outer"
[1, 2].each do |value| # shadows outer 'value'
puts "Block value: #{value}"
end
puts "Outer value: #{value}" # still "outer"
end
shadowing_example
# Block value: 1
# Block value: 2
# Outer value: outer
To access outer variables when block parameters have the same names, Ruby provides block-local variable syntax using semicolons in the parameter list.
def block_local_variables
x, y = 10, 20
[1, 2].each do |value; x, y| # x, y are block-local
x = value * 2
y = value * 3
puts "Block: x=#{x}, y=#{y}"
end
puts "Outer: x=#{x}, y=#{y}" # unchanged
end
block_local_variables
# Block: x=2, y=3
# Block: x=4, y=6
# Outer: x=10, y=20
The yield
expression raises LocalJumpError
when called without an associated block. Always check block_given?
before yielding, or provide default behavior when no block exists.
def safe_yield(value)
if block_given?
yield(value)
else
value.to_s.upcase # default behavior
end
end
puts safe_yield("hello") # => "HELLO"
puts safe_yield("world") { |s| s * 2 } # => "worldworld"
Block return values can be nil
even when blocks appear to return other values. Ruby returns nil
from iteration methods regardless of block return values.
def misleading_returns
result = [1, 2, 3].each { |n| n * 2 } # each returns original array
puts result.class # => Array
result2 = [1, 2, 3].map { |n| n * 2 } # map returns new array
puts result2.class # => Array
result3 = 5.times { |i| i * 3 } # times returns original number
puts result3.class # => Integer
end
Proc and lambda objects handle return statements differently. Procs treat return
as returning from the enclosing method, while lambdas treat return
as returning from the lambda itself.
def proc_vs_lambda_return
my_proc = Proc.new { return "proc return" }
my_lambda = ->(){ return "lambda return" }
# This would cause LocalJumpError if executed
# my_proc.call
result = my_lambda.call # => "lambda return"
puts result
"method end"
end
Block parameters with default values only receive defaults when fewer arguments are yielded, not when nil
is explicitly yielded.
def default_parameter_gotcha
yield(nil)
yield()
end
default_parameter_gotcha do |value = "default"|
puts "Value: #{value.inspect}"
end
# Value: nil # explicit nil, no default used
# Value: "default" # no argument, default used
Reference
Core Block Methods
Method | Parameters | Returns | Description |
---|---|---|---|
yield(*args) |
Splat arguments | Block return value | Executes associated block with arguments |
block_given? |
None | Boolean |
Checks if block was passed to method |
Proc.new |
None | Proc |
Creates Proc from current block |
proc { } |
Block | Proc |
Creates Proc object from block |
lambda { } |
Block | Proc |
Creates lambda (strict Proc) from block |
->() { } |
Block | Proc |
Stabby lambda syntax for creating lambdas |
Block Parameter Syntax
Syntax | Description | Example |
---|---|---|
|a, b| |
Basic parameters | { |x, y| x + y } |
|a, b=default| |
Default values | { |x, y=0| x + y } |
|a, *rest| |
Splat parameters | { |first, *others| [first, others] } |
|**kwargs| |
Keyword arguments | { |**opts| opts[:key] } |
|(x, y)| |
Array destructuring | { |(a, b)| a + b } |
|a; local| |
Block-local variables | { |x; temp| temp = x * 2 } |
Proc vs Lambda Differences
Aspect | Proc | Lambda |
---|---|---|
Argument checking | Flexible, assigns nil to missing |
Strict, raises ArgumentError |
return behavior |
Returns from enclosing method | Returns from lambda only |
Creation syntax | Proc.new , proc |
lambda , -> |
#lambda? method |
Returns false |
Returns true |
Block Return Value Patterns
Method Type | Returns | Block Value Usage |
---|---|---|
each , times |
Original receiver | Ignored |
map , collect |
New array of block returns | Collected into array |
select , filter |
Elements where block is truthy | Used for filtering |
reduce , inject |
Accumulated value | Used in accumulation |
find , detect |
First element where block is truthy | Used for matching |
all? , any? |
Boolean | Used for boolean evaluation |
Common Block Iterator Methods
Method | Parameters | Block Parameters | Returns |
---|---|---|---|
Array#each |
None | |element| |
Original array |
Array#each_with_index |
None | |element, index| |
Original array |
Hash#each |
None | |key, value| |
Original hash |
Integer#times |
None | |index| |
Original integer |
Range#each |
None | |value| |
Original range |
Enumerable#map |
None | |element| |
New array |
Enumerable#select |
None | |element| |
New array/collection |
Error Conditions
Error | Cause | Solution |
---|---|---|
LocalJumpError |
yield without block |
Check block_given? |
ArgumentError |
Lambda argument mismatch | Match parameter count |
LocalJumpError |
return in orphaned Proc |
Use lambda or avoid return |
Variable shadowing | Block parameter shadows outer variable | Use block-local variable syntax |