Don't Inline-Rescue in Ruby

This post was originally published on the New Bamboo blog, before New Bamboo joined thoughtbot in London.


We are currently hiring at New Bamboo. As part of the process, we send candidates a code test to complete at their own leisure. I won’t reveal any details here, but it’s nothing special.

Nevertheless, I have personally reviewed a bunch of these code tests, and there’s something that has caught my attention. A number of applicants misunderstand the usage of the inline rescue construct in Ruby. Not a whole lot of them, but enough that I felt compelled to write this piece in order to help other people out there.

What I am seeing is code like the following:

do_something rescue SomeException

I assume applicants read that as “if SomeException is raised, rescue it and just carry on as if nothing had happened”. Unfortunately, this is not the case. I’ll show with a different example. See this:

do_something rescue nil

What the above does is:

  1. It calls the method do_something
  2. If that raised an exception, it’s rescued immediately. The line evaluates to nil
  3. If no exception was raised, the line evaluates to the return value of do_something

The following is equivalent:

begin
  do_something
rescue
  nil
end

Five lines with their indentation against a clever one-liner. Tempting! If you know that do_something may raise an exception, you use an inline rescue to quickly stop that and continue. Isn’t that neat?

Except that it’s a headache waiting to happen. Consider this code:

def someting_is_wrong?
  rand < 0.01 # Good 99% of the time
end

def do_something
  if something_is_wrong?
    raise MyIgnorableException
  else
    "foo"
  end
end

begin
  do_something
rescue
  nil
end

This code looks ok. However, when we run it, something is amiss. It always returns nil. We expected it to fail (return nil) only in 1% percent of the cases, but it’s actually failing 100% of the time. What’s going on?

Well, that we misspelled the name of the first method, that’s what. Read again: someting_is_wrong?. An “h” is missing.

We are calling a method that doesn’t exist. This raises a NoMethodError, which bubbles up to the rescue, which in turn ends up rescuing more than we wanted. Remember that an unqualified rescue will catch any exceptions that inherit from StandardError. That is dangerous, as it is likely that there will be other problems.

Instead we should use the following invocation:

begin
  do_something
rescue MyIgnorableException
  nil
end

This time we get a NoMethodError straight away. We notice the problem, fix it, and continue on our merry way, safe in the knowledge that we’ll be notified of any unforeseen problems.

In summary, there are two problems with inline rescue of exceptions:

  • The syntax can be misleading, making you think that it’s rescuing only a specific type of exception, when it’s not. It’s rescuing any type of exception (as long as it inherits from StandardError), then evaluating the expression that follows it
  • Rescuing exceptions without sensible filtering will make you miss other problems that you didn’t expect

Therefore, here are two pieces of advice:

  • Never use the unfiltered version of rescue
  • Never use the inline rescue, as it’s effectively an unfiltered rescue

OK, I say “never”, but Avdi Grimm could come up with a decent use case for the inline rescue. Rather than outlining it here, stealing Avdi’s thunder, and making this longer than it needs to be, I’m just going to link to the screencast where he explains it better than I can, in just 3:17 minutes. It’s issue #22 of his excellent Ruby Tapas, and it’s available for free at his blog: Ruby Tapas #22 - Inline Rescue.

Happy hacking!