Capturing blocks

A block can be captured and turned into a Proc, which represents a block of code with an associated context: the closured data.

To capture a block you must specify it as a method’s block parameter, give it a name and specify the input and output types. For example:

  1. def int_to_int(&block : Int32 -> Int32)
  2. block
  3. end
  4. proc = int_to_int { |x| x + 1 }
  5. proc.call(1) # => 2

The above code captures the block of code passed to int_to_int in the block variable, and returns it from the method. The type of proc is Proc(Int32, Int32), a function that accepts a single Int32 argument and returns an Int32.

In this way a block can be saved as a callback:

  1. class Model
  2. def on_save(&block)
  3. @on_save_callback = block
  4. end
  5. def save
  6. if callback = @on_save_callback
  7. callback.call
  8. end
  9. end
  10. end
  11. model = Model.new
  12. model.on_save { puts "Saved!" }
  13. model.save # prints "Saved!"

In the above example the type of &block wasn’t specified: this just means that the captured block doesn’t take any arguments and doesn’t return anything.

Note that if the return type is not specified, nothing gets returned from the proc call:

  1. def some_proc(&block : Int32 ->)
  2. block
  3. end
  4. proc = some_proc { |x| x + 1 }
  5. proc.call(1) # => nil

To have something returned, either specify the return type or use an underscore to allow any return type:

  1. def some_proc(&block : Int32 -> _)
  2. block
  3. end
  4. proc = some_proc { |x| x + 1 }
  5. proc.call(1) # 2
  6. proc = some_proc { |x| x.to_s }
  7. proc.call(1) # "1"

break and next

return and break can’t be used inside a captured block. next can be used and will exit and give the value of the captured block.

with … yield

The default receiver within a captured block can’t be changed by using with ... yield.