Catch and Throw in Ruby

In my last post I had a look at handling exceptions in Ruby and the functionality around that. In this post I’m going to look at how Ruby also allows us to unwind a single block of code without raising an exception. I’ve said before that exceptions should be exceptional – the functionality in Ruby offers a lightweight version of this within a single scope.

Let’s see some code

This is much easier to explain with an example.

def get_number
  rand(100)
end

random_numbers = catch (:random_numbers) do
  result = []
  10.times do
    num = get_number
    throw :random_numbers if num < 10
    result << num
  end
  result
end

p random_numbers

The catch keyword defines a block with a given name. The block is processed normally until a throw statement is encountered.

When a throw statement is encountered Ruby will look up the call stack for a catch statement with the corresponding symbol. It will then unwind the call stack up to that point and terminate the block.

In this example the block will have two possible return values. If no number under 10 is generated the throw statement will never execute and the array of random numbers will be returned. If the throw statement does execute the block will simply return nil. Ruby also allows us to override this behavior – for example, instead of returning nil I might choose to return an empty array. We can do this by specifying a second parameter to the throw statement.

random_numbers = catch (:random_numbers) do
  result = []
  10.times do
    num = get_number
    throw(:random_numbers, []) if num < 10
    result << num
  end
  result
end

Performance

One of the reasons why it’s possibly not a good idea to overuse exceptions in your code is performance. Every time you raise an exception Ruby has to build a stack trace. If your exceptions are exceptional (as they should be) this won’t be a problem. However, if you’re using a large number of exceptions in a tight loop it could have a negative impact on performance. I’m assuming that a catch-throw block doesn’t need to create a stack trace – let’s see if we can get a rough idea of the performance impact.

start = Time.now
10_000_000.times do |i|
  begin
    raise StandardError, "Error #{i}"
  rescue StandardError => error
    error.inspect
  end
end
puts "Raise&Rescue Operation took #{Time.now - start} seconds"

start = Time.now
10_000_000.times do |i|
  catch (:the_loop) do
    throw :the_loop
  end
end
puts "Catch&Throw Operation took #{Time.now - start} seconds"

Performance

As I mentioned, these are very rough performance estimates, but there does seem to be a significant performance difference between using exceptions and using a catch-throw block.

Conclusion

The catch-throw block seems to be a lightweight error handling mechanism – useful when you want to jump out of a nested construct during normal processing. The performance figures point to the same conclusion.

Happy coding.