Catching and throwing in Ruby
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.
Member discussion