Throw, Catch, Raise, Rescue- I'm so confused!

Throw, Catch, Raise, Rescue… I’m so confused!

This guest post is by Avdi Grimm, who is the author of “Exceptional Ruby“, an in-depth guide to exceptions and failure handling in Ruby. RubyLearning readers can get a $3 discount on the book by using code RUBYLEARN. Avdi has been hacking Ruby code for 10 years, and is still loving it. He is chief aeronaut at ShipRise, a consultancy specializing in sustainable software development and in helping geographically dispersed teams work more effectively. He lives in southern Pennsylvania with his wife and four children, and in his copious spare time blogs and podcasts at Virtuous Code and WideTeams.com.

Old keywords, new meanings

Avdi Grimm One of the aspects of Ruby that often confuses newbies coming from other languages is the fact that it has both throw and catch and raise and rescue statements. In this article I’ll try and clear up that confusion.

If you’re familiar with Java, C#, PHP, or C++, you are probably used to using try, catch, and throw for exception handling. You use try to delineate the block in which you expect an exception may occur. You use catch to specify what to do when an exception is raised. And you use throw to raise an exception yourself.

You’ve probably noticed that Ruby has throw and catch… but they don’t seem to be used the way you’re used to in other languages! And there are also these “begin“, “raise” and “rescue” statements that seem to do the same thing. What’s going on here?

Getting out fast

If you’ve done much programming in another language like Java, you may have noticed that exceptions are sometimes used for non-error situations. “exceptions for control flow” is a technique developers sometimes turn to when they want an “early escape” from a particular path of execution.

For instance, imagine some code that scrapes a series of web pages, looking for one that contains a particular text string.

def show_rank_for(target, query)
  rank = nil
  each_google_result_page(query, 6) do |page, page_index|
    each_google_result(page) do |result, result_index|
      if result.text.include?(target)
        rank = (page_index * 10) + result_index
      end
    end
  end
  puts "#{target} is ranked #{rank} for search '#{query}'"
end

show_rank_for("avdi.org", "nonesuch")

(For brevity, I’ve excluded the definitions of the #each_google_result_page and #each_google_result methods. You can view the full source at https://gist.github.com/1075364.)

Fetching pages and parsing them is time-consuming. What if the target text is found on page 2? This code will keep right on going until it hits the max number of result pages (here specified as 6).

It would be nice if we could end the search as soon as we find a matching result. We might think to use the break keyword, which “breaks out” of a loop’s execution. But break only breaks out of the immediately surrounding loop, and here we have a loop inside another loop.

This is a situation where we might come up with the idea of using an exception to break out of the two levels of looping. But exceptions are supposed to be for unexpected failures, and finding the results we were looking for is neither unexpected, nor a failure! What to do?

Throwing Ruby a fast ball

Ruby has given us a tool for just this situation. Unlike in other languages, Ruby’s throw and catch are not used for exceptions. Instead, they provide a way to terminate execution early when no further work is needed. Their behavior is very similar to that of exceptions, but they are intended for very different situations.

Let’s look at how we can use catch and throw to end the web search as soon as we find a result:

def show_rank_for(target, query)
  rank = catch(:rank) {
    each_google_result_page(query, 6) do |page, page_index|
      each_google_result(page) do |result, result_index|
        if result.text.include?(target)
          throw :rank, (page_index * 10) + result_index
        end
      end
    end
    "<not found>"
  }
  puts "#{target} is ranked #{rank} for search '#{query}'"
end

This time we’ve wrapped the whole search in a catch{...} block. We tell the catch block what symbol to catch, in this case :rank. When the result we are looking for is found, instead of setting a variable we throw the symbol :rank. We also give throw a second parameter, the search result :rank. This second parameter is the throw’s “payload”.

The throw “throws” execution up to the catch block, breaking out of all the intervening blocks and method calls. Because we gave the throw and catch the same symbol (:rank), the catch block is matched to the throw and the thrown symbol is “caught”.

The rank value that we gave as a payload to throw now becomes the return value of the catch block. We assign the result value to a variable, and proceed normally.

What if the search string is never found, and throw is never called? In that case, the loops will finish, and the return value of the catch block will be the value of the last statement in the block. We provide a default value (“”) for just this possibility.

catch and throw in the real world

The Rack and Sinatra projects provide a great example of how throw and catch can be used to terminate execution early. Sinatra’s #last_modified method looks at the HTTP headers supplied by the client and, if they indicate the client already has the most recent version of the page, immediately ends the action and returns a “Not modified” code. Any expensive processing that would have been incurred by executing the full action is avoided.

get '/foo' do
  last_modified some_timestamp
  # ...expensive GET logic...
end

Here’s a simplified version of the #last_modified implementation. Note that it throws the :halt symbol. Rack catches this symbol, and uses the supplied response to immediately reply to the HTTP client. This works no matter how many levels deep in method calls the throw was invoked.

def last_modified(time)
  response['Last-Modified'] = time
  if request.env['HTTP_IF_MODIFIED_SINCE'] > time
    throw :halt, response
  end
end

The way Rack uses catch/throw illustrates an important point: the throw call does not have to be in the same method as the catch block.

Conclusion

Ruby is a language that tries to anticipate your needs as a programmer. One common need is a way to terminate execution early when we find there is no further work to be done. Unlike in some languages, where we would have to either abuse the exception mechanism or use multiple loop breaks and method returns to achieve the same effect, Ruby provides us with the catch and throw mechanism to quickly and cleanly make an early escape. This leaves begin/raise/rescue free to be used for errors, and nothing else.

I hope you found this article valuable. Feel free to ask questions and give feedback in the comments section of this post. Thanks!

comments powered by Disqus