Block forwarding

To forward captured blocks, you use a block argument, prefixing an expression with &:

  1. def capture(&block)
  2. block
  3. end
  4. def invoke(&block)
  5. block.call
  6. end
  7. proc = capture { puts "Hello" }
  8. 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:

  1. invoke(proc) # Error: wrong number of arguments for 'invoke' (1 for 0)

You can actually pass a proc to a method that yields:

  1. def capture(&block)
  2. block
  3. end
  4. def twice(&)
  5. yield
  6. yield
  7. end
  8. proc = capture { puts "Hello" }
  9. twice &proc

The above is simply rewritten to:

  1. proc = capture { puts "Hello" }
  2. twice do
  3. proc.call
  4. end

Or, combining the & and -> syntaxes:

  1. twice &->{ puts "Hello" }

Or:

  1. def say_hello
  2. puts "Hello"
  3. end
  4. twice &->say_hello

Forwarding non-captured blocks

To forward non-captured blocks, you must use yield:

  1. def foo(&)
  2. yield 1
  3. end
  4. def wrap_foo(&)
  5. puts "Before foo"
  6. foo do |x|
  7. yield x
  8. end
  9. puts "After foo"
  10. end
  11. wrap_foo do |i|
  12. puts i
  13. end
  14. # Output:
  15. # Before foo
  16. # 1
  17. # 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:

  1. def foo(&)
  2. yield 1
  3. end
  4. def wrap_foo(&block : Int32 -> _)
  5. puts "Before foo"
  6. foo(&block)
  7. puts "After foo"
  8. end
  9. wrap_foo do |i|
  10. puts i
  11. end
  12. # Output:
  13. # Before foo
  14. # 1
  15. # 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:

  1. foo_forward do |i|
  2. break # error
  3. end

In short, avoid &block forwarding when yield is involved.