Ruby tidbit: When rescue doesn’t
Posted by Bill on July 31st, 2008I learned a valuable lesson this morning, and I thought I’d share it with you. To deal with error conditions, Ruby includes, as do other languages, exception handling. This allows you to put all your code that might generate one or more errors in a block, and deal with any errors that do occur in another block. Far preferable to days of old when we had to check the return value of any call that may generate an error, and deal with it on the spot, each in unique fashion. Exception handling is much cleaner and more manageable:
begin
# Do stuff that may fail here
rescue
# Deal with those failures here
endMost articles dealing with Ruby exception handling will tell you that the way to catch exceptions is with the “rescue” line. But it turns out, that’s only part of the truth. An example done in Ruby’s irb console:
irb(main):001:0> begin
irb(main):002:1* raise "Haha, you missed me!"
irb(main):003:1> rescue
irb(main):004:1> puts "No I didn't!"
irb(main):005:1> end
No I didn't!So far so good. But now watch this:
irb(main):006:0> begin
irb(main):007:1* raise Exception.new("Haha, you missed me!")
irb(main):008:1> rescue
irb(main):009:1> puts "No I didn't!"
irb(main):010:1> end
(irb):7:in `irb_binding': Haha, you missed me! (Exception)Woops! In that case, rescue really did miss it. But why? Doesn’t “rescue” mean rescue everything? I was surprised to find out that it doesn’t. One more example should clear things up:
irb(main):016:0> begin
irb(main):017:1* raise "Better luck next time!"
irb(main):018:1> rescue => e
irb(main):019:1> puts("I caught a " + e.class.to_s)
irb(main):020:1> end
I caught a RuntimeError
=> nil
irb(main):021:0> begin
irb(main):022:1* raise StandardError.new("Better luck next time!")
irb(main):023:1> rescue => e
irb(main):024:1> puts("I caught a " + e.class.to_s)
irb(main):025:1> end
I caught a StandardError
=> nil
irb(main):026:0> begin
irb(main):027:1* raise Exception.new("Better luck next time!")
irb(main):028:1> rescue => e
irb(main):029:1> puts("I caught a " + e.class.to_s)
irb(main):030:1> end
(irb):27:in `irb_binding': Better luck next time! (Exception)Ah-ha! So while it seems that “rescue” would be the most generic way of dealing with exceptions of all kinds, it really isn’t. It will catch StandardError (of which RuntimeError is a subclass), but it misses Exception. The most generic exception handler turns to be “rescue Exception”:
begin
# Do stuff here
rescue Exception
# I will catch everything, even stuff that "rescue" misses
endLesson learned: unless you know the only thing you might have to deal with is StandardError or one of its subclasses, it’s better to use “rescue Exception” than just “rescue”. Nevermind documents that suggest that “rescue” catches exceptions; that’s true, but misleading - it really only catches certain kinds of them.




7 responses to "Ruby tidbit: When rescue doesn’t"