Skip to content

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:

def int_to_int(&block : Int32 -> Int32)
  block
end

proc = int_to_int { |x| x + 1 }
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:

class Model
  def on_save(&block)
    @on_save_callback = block
  end

  def save
    if callback = @on_save_callback
      callback.call
    end
  end
end

model = Model.new
model.on_save { puts "Saved!" }
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:

def some_proc(&block : Int32 ->)
  block
end

proc = some_proc { |x| x + 1 }
proc.call(1) # void

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

def some_proc(&block : Int32 -> _)
  block
end

proc = some_proc { |x| x + 1 }
proc.call(1) # 2

proc = some_proc { |x| x.to_s }
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.