Block forwarding
To forward captured blocks, you use a block argument, prefixing an expression with &
:
def capture(&block)
block
end
def invoke(&block)
block.call
end
proc = capture { puts "Hello" }
invoke(&proc) # prints "Hello"
In the above example, invoke
receives a block. We can’t pass proc
directly to it because invoke
doesn’t receive regular arguments, just a block argument. We use &
to specify that we really want to pass proc
as the block argument. Otherwise:
invoke(proc) # Error: wrong number of arguments for 'invoke' (1 for 0)
You can actually pass a proc to a method that yields:
def capture(&block)
block
end
def twice(&)
yield
yield
end
proc = capture { puts "Hello" }
twice &proc
The above is simply rewritten to:
proc = capture { puts "Hello" }
twice do
proc.call
end
Or, combining the &
and ->
syntaxes:
twice &->{ puts "Hello" }
Or:
def say_hello
puts "Hello"
end
twice &->say_hello
Forwarding non-captured blocks
To forward non-captured blocks, you must use yield
:
def foo(&)
yield 1
end
def wrap_foo(&)
puts "Before foo"
foo do |x|
yield x
end
puts "After foo"
end
wrap_foo do |i|
puts i
end
# Output:
# Before foo
# 1
# After foo
You can also use the &block
syntax to forward blocks, but then you have to at least specify the input types, and the generated code will involve closures and will be slower:
def foo(&)
yield 1
end
def wrap_foo(&block : Int32 -> _)
puts "Before foo"
foo(&block)
puts "After foo"
end
wrap_foo do |i|
puts i
end
# Output:
# Before foo
# 1
# After foo
Try to avoid forwarding blocks like this if doing yield
is enough. There’s also the issue that break
and next
are not allowed inside captured blocks, so the following won’t work when using &block
forwarding:
foo_forward do |i|
break # error
end
In short, avoid &block
forwarding when yield
is involved.