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 argument, 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 have 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) # void

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.