Rescue's elegant trick for knowing which exceptions to catch

 

If you've worked with Ruby's exceptions before, you know you can specify which exceptions get rescued and which are not:

begin
  raise ArgumentError
rescue ArgumentError
  # Rescues the `ArgumentError`
end

...and you probably know that when you rescue a "parent" you rescue all of its "children" as well.

begin 
  raise ArgumentError
rescue StandardError
  # Rescues `ArgumentError`, because it inherits from 
  # `StandardError`
end

When I say "parent" and "child" I'm simply referring to class inheritance. Somewhere deep in the Ruby source code there is something equivalent to this:

class ArgumentError < StandardError
   ...
end

An interesting trick

Here's my question: how does Ruby know if any given exception inherits from the class you specified?

The most obvious approach would be to use the is_a? or kind_of? method. We could imagine it looking like this:

if the_exception.is_a?(StandardError)
   # do the rescue
end

But that's not what happens. Instead, Ruby uses the more interesting === operator.

if StandardError === the_exception
   # do the rescue
end

If you've never used a === b, it usually answers the question "does a inherently belong to the group defined by b"? Here are some examples:

(1..10) === 5             # true
('a'..'f') === "z"        # false

String === "hello"        # true
String === 1              # false

/[0-9]{3}/ === "hello123" # true
/[0-9]{3}/ === "hello"    # false

Because === is just an ordinary ruby method like ==, we can define it ourself:

class RedThings
   def self.===(thing)
     thing.color == :red
   end
end

So, what do we know? We know that rescue uses === to determine which exceptions get rescued. And we know that we can define our own === method. That means we can create a class that decides on-the-fly which exceptions are rescued:

class SevereMatcher
  def self.===(exception)
    exception.message =~ /severe/    
  end
end

begin
  raise RuntimeError, "Something severe happened"
rescue SevereMatcher
  # rescues all exceptions with the word "severe" in
  # the message, regardless of class.
end

Once you know this trick, the only limit is your imagination.

Conclusion

I'll admit: you may not ever need to create a dynamic exception matcher. But this is a really interesting example of how a seemingly-trivial implementation detail like using === instead of kind_of? makes Ruby much more flexible and interesting.

Get the Honeybadger newsletter

Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo
    Starr Horne

    Starr Horne is a Rubyist and Chief JavaScripter at Honeybadger.io. When she's not neck-deep in other people's bugs, she enjoys making furniture with traditional hand-tools, reading history and brewing beer in her garage in Seattle.

    More articles by Starr Horne
    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required