Computer >> Computer tutorials >  >> Programming >> ruby

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