1 min read

Catching and throwing in Ruby

Nothing to do with exceptions, ruby's catch and throw
Catching and throwing in Ruby
Photo by Diana Polekhina / Unsplash

Nothing to do with exceptions, ruby's catch and throw.

I've known but never used ruby's catch and throw until tackling an Advent of Code problem. This particular challenge had me building multiple nested iterators, in short scanning a 3d array – a set of game boards which contain rows which contain cells.

boards.each do |board|
  board.each_with_index do |row, row_idx|
    row.each_with_index do |col, col_idx|
      # if <condition> exit all the enumerators
    end
  end
end

So the question was, how to simply break out of all the iterators if a condition was met on line 4. One simple way to do this would be to factor this block out to a method and have a return statement here but there is another way.

While ruby does have first-class support for exception handling, catch and throw are not used for exceptions but for control-flow. They are made for exactly the problem I'm trying so solve; breaking out of a stack several levels deep and returning control to the a point much earlier.

This is what they look like:

catch(:something) do
  loop do
    throw :something
  end
end

The throw statement immediately returns to calling block. In fact we can use this to return values to the outer block

value = catch(:something) do
  loop do
    throw :something, 'foobar'
  end
end

Here, value will become equal to foobar at the point line 3 executes.

This helps me write my block like this:

score = catch(:won) do
  boards.each do |board|
    board.each_with_index do |row, row_idx|
      row.each_with_index do |col, col_idx|
        # do some work
        throw :won, some_value if condition
      end
    end
  end
end

The full block of code is on GitHub.