1. Computing

Throw and Catch


In Ruby, exceptions are raised to denote something exceptional (and usually bad) has happened. However, Ruby also provides a cousin to the exceptions: throw and catch.

First off, let's go back and talk about C again. In C, there is a feature called goto. It jumps out of the current execution context and to another point in the function, usually straight out of any loops or conditional statements it may have been in (though not into them). It's useful in a tight situation, or to get out of deeply nested loops without jumping through a lot of hoops, but generally considered something that produces "spaghetti code," or code that follows irrational and difficult to see execution paths. However, there was one use case in that explanation that survives into Ruby: jumping out of deeply nested loops.

Imagine the following situation: you have a tree structure. This structure stores arrays of strings, and you have to search through such a data structure for something specific. Doing this will involve several nested loops to first iterate through the tree structure, then each of the arrays and finally (possibly) through each of the strings. When what you want to find has been found, you want to immediately get out of all these loops and do whatever it is you want to do with whatever it was you found.

The following pseudocode demonstrates how you'd jump out of nested loops with a value.

value = catch(:done) do
  tree.each do|t|
    t.list.each do|l|
      l.split(' ').each do|w|
        throw(:done, l) if w == "monkey"

puts "Value with monkey is #{value}"

This looks useful, right? There's some controversy here. Typically, Ruby programs are not even nested this deeply. Rubyists tend to like to decompose such things into methods and avoid such deep nesting and indentation. It's easier to read and understand that way.

Another thing to think about is performance. The throw and catch mechanism is essentially an exception, and exceptions are slow. If you're using it like a control structure, you may get into the habit of thinking of it as a control structure, and it just isn't. It's not fast, and in a tight loop that might mean the difference between an acceptably fast (let's face it, nothing in Ruby is really fast) and something too slow to use.

Though, if you're dead-set on using it, here is another variant. Maybe you don't want to throw a variable at all, and just want to jump out of a loop? Simply leave the variable off.

catch(:no_more_words) do
  words.each do|w|
    # …
    throw(:no_more_words) if word == "end"

Also note that, like exceptions, thrown objects can be thrown right out of a method. The stack unwinds just as with exceptions. You can use this to break out of deeply nested method calls, but doing this is doubleplus ungood. Not only will the reader not know what throws such a symbol and from where, but why have no clear idea of what was really meant. Is this supposed to happen? It's not an exception, so it must not be that bad. Or maybe it is really bad? Who knows.

The same things can be said about exceptions. In the end, both catch/throw and raise/rescue do the same things in slightly different ways. The differences are in how they are used. Exceptions are used when something goes really wrong. You know that you must catch them and, if you fail to do some, have a good idea of what went wrong. The catch/throw mechanism, on the other hand, has no established use case. You really have no idea what they're really about.

A good rule of thumb for using catch/throw is to use them to only jump out of deeply nested (or just long or ungainly) loops or conditionals. Certainly never throw something so far is falls off the editor screen, that will just create confusion. And absolutely never throw something so far it falls out of the method. And never, never, never throw something so far you expect the user to catch it. Even if you disagree with the reasoning here, there's one overarching reason to not do this: it isn't idiomatic Ruby. Other Ruby programmers won't expect it and it will throw them for a loop (no pun intended). An not in a good "Oh, that's quite clever" way, in a "Gah! Why did he do it that way?!" way.

  1. About.com
  2. Computing
  3. Ruby
  4. Beginning Ruby
  5. Control Structures
  6. Throw and Catch

©2014 About.com. All rights reserved.