1. Technology
You can opt-out at any time. Please refer to our privacy policy for contact information.

Exceptions

By

Whenever you call a method, there's always a chance that it will fail in some way. It will fail to open a network connection, or it will fail to open a file, or it will try to divide by zero or really anything that will make the method "fail." So what do you do with a failed method? How do you even know it failed?

Historically (and by "historically," I mean in C) errors are implemented by returning an invalid value. For example, if the function is supposed to return a pointer, it will return NULL or if it was supposed to return a number, it will return -1. But there are some problem in doing error detection and handling with return values.

  • It's a royal pain to check the return value of every single function you call. Look at any "good" C code and for every function call of consequence, there are at least three extra lines of code to check the return value, compare it to something it knows is "bad" and do something (usually exit the program) if there was an error. This makes even simple code very difficult to read, and perhaps more importantly, it's a lot of typing. Let's face it, programmers are lazy beasts by design: programmers try to do everything in the most efficient manner possible. If they feel they don't absolutely have to type out all that error checking, they won't do it.

  • Occasionally you'll run into a function where any return value is valid. How do you detect an error then? Usually a global variable is used, but that adds its own heap of problems when you try to make your program threaded.

Exceptions

Enter exceptions, a clean solution to this problem. Exceptions are objects (like everything else in Ruby) that saves some information about the current state of the program. In particular, it saves the call stack, so you know which method called which method which eventually called the method that failed. This is invaluable debugging information, it immediately tells you how the error came to be.

Exceptions are raised (not thrown as in other languages, that's something slightly different in Ruby). When an exception is raised, everything stops. The stack is unwound looking for a rescue statement. That is to say Ruby searches upward through the call stack and looks for a rescue statement, and each method it passes through while going upward implicitly returns no value.

The process Ruby goes through while unwinding the stack is as follows:

  1. An exception has been raised. Assign this exception to the special variable $!.
  2. Is the current execution context in a begin/rescue statement? If not, end the current method call and go up to the method that called it.
  3. If a begin/rescue statement is found, iterate through each of the rescue expressions. If any of them have a matching class, execute that block. Execute any ensure expressions there are as well.
  4. If the bottom of the call stack is reached, there was an uncaught exception. Print the exception information and exit the program.

As mentioned above, exceptions are raised using the raise keyword. It is typically used with a single argument: an instance of an Exception class. Only Exception objects may be raised, only they can carry the stack backtrace information. As a convenience, the raise keyword may also take a single string argument which is used as the message for a RuntimeError exception. A special case, raise with no arguments is discussed below.

Catching exceptions is done with a begin/rescue statement. The rescue expression typically defines a type of exception that it wants to catch. For instance, if you wanted to catch only ZeroDivisionError exceptions, that would be specified here. The type is then followed by => variable_name, if the exception matches it will be assigned to the variable name in the rescue clause. More than one rescue clause may be in each begin/rescue statement. In addition to rescue, there may also be else, which is called if none of the rescue blocks match and ensure, which is always called whether there was a caught, uncaught or no exception at all.


#!/usr/bin/env ruby

def foo(a)
  begin
    bar(a)
  rescue Exception => e
    puts "Caught a #{e.class}"
  end
end

def bar(a)
  begin
    baz(a)
  rescue ZeroDivisionError => e
    puts "Caught a ZeroDivisionError"
  ensure
    puts "Leaving the begin/rescue statement"
  end
end

def baz(a)
  case a
  when :divide_by_zero
    10 / 0
  when :syntax_error
    eval '10 ******* 10'
  end
end

foo(:divide_by_zero)
foo(:syntax_error)

In this example, there are three methods that all lead to baz, which can generate two types of exceptions. In this case, bar knows how to catch and deal with a ZeroDivisionError, so when baz tries to divide by zero, it catches it. The foo method, on the other hand, knows how to catch all exceptions. Since only Exception objects may be thrown, catching Exception will catch all exceptions, so it will catch the SyntaxError exception thrown by baz. Another way of catching all exceptions regardless of type is to put no class or variable name after the rescue keyword.

Helpless, but Attentive

Sometimes there's nothing you can do about an error. You just can't make do without that file you needed. However, you may want to give more detailed information about the exception nearer to where it was thrown. Since the program should probably exit after the message is printed, you can just exit, right?

Wrong. This is a very bad idea. It's bad form to exit your code at a deeply nested level, and especially if this code is going to end up in a library. Instead, you can re-rased the exception. This is useful if you merely want to examine the exception being raised or print some information about it but not actually handle it. Code further up in the call stack can handle it, or it can go unhandled and the program will exit (as you wanted). To re-raise an exception, use the raise keyword without any arguments.


begin
  File.open('doest_exist.txt', 'r')
rescue SystemCallError => sce
  puts "Failed to open the file, this isn't good!"
  puts sce
  raise
end
  1. About.com
  2. Technology
  3. Ruby
  4. Beginning Ruby
  5. Control Structures
  6. Exceptions

©2014 About.com. All rights reserved.