Ruby / Exception handling

From WhyNotWiki

Jump to: navigation, search


Exception handling  edit   (Category  edit)


Contents

[edit] Exception hierarchy

  • Exception
    • fatal
    • NoMemoryError
    • ScriptError
      • LoadError
      • NotImplementedError
      • SyntaxError
    • SignalException
      • Interrupt
    • StandardError
      • ArgumentError
      • IOError
        • EOFError
      • IndexError
      • LocalJumpError
      • NameError
        • NoMethodError
      • RangeError
        • FloatDomainError
      • RegexpError
      • RuntimeError
      • SecurityError
      • SystemCallError
        • Errorno::__ (ENOENT, etc.) (system-dependent)
      • ThreadError
      • TypeError
      • ZeroDivisionError
    • SystemExit
    • SystemStackError

[edit] The rescue statement modifier

[edit] Intro

rescue doesn't always have to be used with a begin block. You can do rescues even more concisely.

irb -> raise 'an error' rescue "Hey, there was an error!"
    => "Hey, there was an error!"

Or, most concisely of all:

irb -> raise 'an error' rescue nil
    => nil

It really doesn't get more concise than that for exception handling. If you just want to silence exceptions and you don't want to actually do any intelligent handling of them then rescue nil is your ticket.

[edit] Details

When you use rescue as a statement modifier, it rescues StandardError and its subclasses. You can't specify which class of exception(s) that you want to raise like you can with the normal version of rescue (rescue Exception => exception, for example).

Update: Perhaps it is possible after all. It looks like Rails is doing it right here (./vendor/rails/railties/lib/dispatcher.rb):

    rescue Exception => exception  # errors from CGI dispatch
      failsafe_response(output, '500 Internal Server Error', exception) do
        controller ||= ApplicationController rescue LoadError nil
        controller ||= ActionController::Base
        controller.process_with_exception(request, response, exception).out(output)
      end
    ensure

You also can't do such things as "ensure" when you use this style of rescue.

[edit] Caveat: The thing you pass to rescue is what it should do when there's a StandardError -- not the class of exceptions that it should rescue

That fact is a bit confusing, because it's different from the normal begin/rescue blocks.

irb -> begin
         raise "A runtime error"
       rescue RuntimeError
         "The thing that I want to happen when there's a RuntimeError"
       end
    => "The thing that I want to happen when there's a RuntimeError"

but:

irb -> raise "A runtime error" rescue "The thing that I want to happen when there's a RuntimeError"
    => "The thing that I want to happen when there's a RuntimeError"

If you forget this fact, you may be surprised when the error class itself is returned:

irb -> raise "A runtime error" rescue TypeError
    => TypeError

[edit] As soon as the error is rescued and you're no longer in the rescue block, $ERROR_INFO/$! resets to nil.

irb -> require 'English'
    => true

irb -> $ERROR_INFO
    => nil

irb -> raise "A runtime error" rescue puts $ERROR_INFO.inspect
#<RuntimeError: A runtime error>
    => nil

irb -> $ERROR_INFO
    => nil
irb -> $!
    => nil

[edit] Example: print errors without stopping

Use this trick when you want to see error messages that are raised but you want execution to continue:

irb -> NotARealClass.new() rescue $stderr.puts $!.inspect
#<NameError: uninitialized constant NotARealClass>
    => nil

or just:

irb -> NotARealClass.new() rescue $stderr.puts $!
uninitialized constant NotARealClass
    => nil

http://codeforpeople.com/lib/ruby/autorequire/autorequire-0.0.0/README

    # rescues error and prints its message- support method for samples/*
    #
    def print_error
      yield
    rescue Exception => e
      puts "#{ e } (#{ e.class })!"
    end

[edit] Like all things in Ruby, rescue has a return value

This can be useful, for example, when you need to assign a default value to a variable if an error occurs.

irb -> an_important_variable = SomethingThatWillRaiseAnError.do_calculation rescue "a sane default value"
    => "a sane default value"

You may be thinking (if you come from a background in other, less orthogonal languages), how can you do things like rescue puts $!.inspect? You can do that because puts, like all methods and blocks, has a return value.

irb -> a = puts "whatever"
whatever
    => nil

irb -> puts a
nil
    => nil

irb -> a = raise 'arg!' rescue puts 'whatever'
whatever
    => nil

irb -> puts a
nil
    => nil

[edit] Can have an ensure without a begin block

def something_that_might_raise_an_error
  raise "an error"
ensure
  puts "that we print this no matter what"
end

something_that_might_raise_an_error rescue puts $!
that we print this no matter what
an error

[edit] Re-raising errors

[Ruby / Features (category)]

[edit] Application thereof: Rescue in order to inspect the error more closely, but then "change your mind"

This application of exception re-raising has been made use of in a patch to RubyGems' require() method.

The problem is that Ruby's exception handling mechanism only lets you specify the class of the exception that you want to rescue:

  rescue LoadError => exception

But sometimes just being able to specify a class causes too broad a category of errors to be rescued.

Fortunately, Ruby makes it very easy to inspect the details of the error after you've rescued it and to "change your mind" and re-raise the error if you decide you're not interested in rescuing it after all.

One use of this is with LoadErrors. If you only want to handle LoadErrors related to the failure to load a file named "A", then you can write a rescue block that only handles those specific errors, and causes all other errors to be re-raised as if they had not been rescued in the first place!

  begin
    ...
  rescue LoadError => exception
    if exception.message == "no such file to load -- A"
      # Handle it. Maybe dynamically *create* the 'A' file, for instance.
      ...
    else
      # We've "changed our mind". Don't know how to handle it after all. Re-raise it and let someone else worry about handling it.
      raise
    end
  end
Personal tools