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
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.
To the extent possible under law, the persons who contributed to this workhave waived
all copyright and related or neighboring rights to this workby associating CC0 with it.
https://crystal-lang.org/docs/syntax_and_semantics/block_forwarding.html