Programmers who come to Ruby from other languages — particularly C++, C♯ or Java — tend to misuse Ruby’s throw statement. They type throw when they mean to type raise.
What’s the difference?
Like C++, C♯ and Java, Ruby has first-class support for exceptions. Exceptions can be thrown using the raise statement and caught using the rescue keyword. Rubyists often speak of “errors” rather than “exceptions.” This is because it’s accepted that the names of Ruby exception classes generally end in Error rather than Exception, excepting the base class for exceptions which is called Exception. (Still with me?)
You may say, “Okay, so I’ll start saying ‘error’ when I mean ‘exception.’ But why can’t I at least type the throw and catch keywords I’m used to typing?”
Ruby has a distinctive language feature that consists of pairs of throw and catch statements. The throw-catch paradigm works similarly to raise and rescue. When Ruby encounters a throw statement, like a good matchmaker, she walks back up the execution stack (“catch me a catch!”) looking for a suitable catch. Here’s some code to illustrate.
class Tevye def live sunrise sunset l_chaim! toil_in_anatevka :pauper end def rich_man? false end private def method_missing(method, *args) nil end def toil_in_anatevka if self.rich_man? throw :miracle_of_miracles, 'Emigrated to the Cayman Islands' end end end poor_tevye = Tevye.new result = catch(:miracle_of_miracles) do poor_tevye.live end
What is inevitable result of toiling in Anatevka? Nothing too surprising: it is being a :pauper.
But let’s change Tevye’s fortunes a bit and reward him richly for his labor. Take a look.
rich_tevye = Tevye.new def rich_tevye.rich_man? true end result = catch(:miracle_of_miracles) do rich_tevye.live end
What comes of Tevye’s hard work if deus ex machina descends upon the village? Tevye gets to retire to the western Caribbean, and his wife, Golde, grows a proper double chin.
Just as an unrescued error causes the Ruby process to crash, so a throw without a matching catch will raise an error (which in turn you can rescue).
It’s not just semantics
You cannot simply throw and catch wherever you would otherwise raise and rescue. There are differences beyond that the latter is for representing exception conditions and the former for other kinds of stack popping.
To begin with, (as you would expect from your experiences with other languages) rescue NameError rescues errors of type NameError as well as subclasses such as NoMethodError. By analogy you might presume that catch Entertainer would handle all these throw statements:
• throw Entertainer
• throw Entertainer.new
• throw BroadwayStar
• throw BroadwayStar.new
You would be wrong: catch Entertainer handles only throw Entertainer.
Another important difference is that throw can take two arguments, as demonstrated in the Tevye class above. The second argument to throw is what Ruby returns from the matching catch statement (also demonstrated above).
A further distinction is the fact that rescue can appear more than once in a begin-end or def-end pair. The first matching rescue in a sequence wins.
begin do_something_error_prone rescue AParticularKindOfError # Insert heroism here. rescue Exception write_to_error_log raise end
By contrast, catch is a block construct. Multiple catch statements must be nested in order to appear together.
catch :foo do catch :bar do do_something_that_can_throw_foo_or_bar end end
Prior to Ruby v1.9, only symbols (for example, :pauper) could be thrown and caught. This restriction was removed, so now you can throw any Ruby object and catch it successfully.
The bottom line
When should you use this language feature? Not very often. As you probably noticed, my contrived example about peasant life in Tsarist Russia is a questionable application of the throw-catch pattern. There are two plausible examples in the Pickaxe book (http://ruby-doc.org/docs/ProgrammingRuby/html/tut_exceptions.html#S4). Fundamentally, throw-catch is a way to escape from the bowels of multiple layers of control flow without abusing raise-rescue for this purpose.
But if you have so many layers of flow control that you’re tempted to throw in order to get out of them quickly, consider trying to eliminate the layers first.
(I adapted this protip from a weblog post I wrote in May 2010.)