Ruby Did You Know That: #2 (Exception Handling)

Posted by -

This is an interesting one most people don't know or consider in their day-to-day Ruby coding: how Exception Handling works. More specifically, how naming which exceptions to handle works.

When we need to catch an exception:

 begin
   raise "Silly Error"
 rescue StandardError, RuntimeError => err
   p err
 end

we name the exception types to catch: StandardError, RuntimeError. We can actually put arbitrary code there:

 begin
   raise "Silly Error"
 rescue (puts 'picking a class'; [IOError, RuntimeError].shuffle.first) => err
 end

which sometimes returns err, the exception object, and sometimes doesn't catch the RuntimeError! That's important to note: like optional arguments, the "which exceptions should I catch" code is invoked each time it is encountered.

Of course, we shouldn't stop there. Exactly what happens after that "which exception" code is evaluated? It turns out, it's our favorite Ruby pattern matcher at work: #===.

As you likely know, Module#=== returns true if it is passed an instance of the given module/class. So when you write:

 begin
   raise StandardError.new('something')
 rescue IOError, StandardError => err
   error_handling()
 end

When the exception hits, Ruby runs the equivalent of:

 if ([IOError, StandardError].any? { |type| type === $! })
   err = $1
   error_handling()
 else
   raise err
 end

That means, in the Ruby tradition, we can do all kinds of horrible things (kidding!):

 BigErrorMessagesOnly = Object.new
 def BigErrorMessagesOnly.===(other)
   other.message.size > 50
 end

This little object BigErrorMessagesOnly will only match exceptions with messages longer than 50 characters. Can't have non-descriptive error messages, can we?

 begin
   raise 'something'
 rescue BigErrorMessagesOnly
 end

 #=> RuntimeError: something

 begin
   raise 'a very long message' * 10
 rescue BigErrorMessagesOnly
   puts "Saved!"
 end

 #=> "Saved!"

Naturally, this cool behavior makes static analysis harder. Laser will initially only work with classes/modules as rescue arguments, but once pure-function emulation works, any inferrably-constant argument can be discovered statically.


Enjoy this article? Then feel free to:


Comments