lhkkug
Last Updated: February 25, 2016
·
14.31K
· njonsson
443889211900a5dd2f1c85c0f4fa2d54

Don’t confuse Ruby’s ‘throw’ statement with ‘raise’

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.)

Say Thanks
Respond