Even the most carefully written program will sometimes encounter unforeseen errors. For example, if you write a program that needs to read some data from disk, it works on the assumption that the specified disk is actually available and the data is valid. If your program does calculations based on user input, it works on the assumption that the input is suitable to be used in a calculation.
Although you may try to anticipate some potential problems before they arise—for example, by writing code to check that a file exists before reading data from it or checking that user input is numerical before doing a calculation—you will never be able to predict every possible problem in advance.
The user may remove a data disk after you’ve already started reading data from it, for example; or some obscure calculation may yield 0 just before your code attempts to divide by this value. When you know that there is the possibility that your code may be “broken” by some unforeseen circumstances at runtime, you can attempt to avoid disaster by using exception handling.
An exception is an error that is packaged into an object. The object is an instance of the Exception class (or one of its descendants). You can handle exceptions by trapping the Exception object, optionally using information that it contains (to print an appropriate error message, for instance) and taking any actions needed to recover from the error—perhaps by closing any files that are still open or assigning a sensible value to a variable that may have been assigned some nonsensical value as the result of an erroneous calculation.
The basic syntax of exception handling can be summarized as follows:
begin # Some code which may cause an exception rescue <Exception Class> # Code to recover from the exception end
When an exception is unhandled, your program may crash, and Ruby is likely to display a relatively unfriendly error message:
div_by_zero.rb
x = 1/0 puts( x )
The program terminates with this error:
C:/bookofruby/ch9/div_by_zero.rb:3:in `/': divided by 0 (ZeroDivisionError) from C:/bookofruby/ch9/div_by_zero.rb:3:in `<main>'
To prevent this from happening, you should handle exceptions yourself. Here is an example of an exception handler that deals with an attempt to divide by zero:
exception1.rb
begin x = 1/0 rescue Exception x = 0 puts( $!.class ) puts( $! ) end puts( x )
When this runs, the code following rescue Exception
executes and displays this:
ZeroDivisionError divided by 0 0
The code between begin
and end
is my exception-handling block. I’ve placed the troublesome code after begin
. When an exception occurs, it is handled in the section beginning with rescue
. The first thing I’ve done is to set the variable x
to a meaningful value. Next come these two inscrutable statements:
puts( $!.class ) puts( $! )
In Ruby, $!
is a global variable to which is assigned the last exception. Printing $!.class
displays the class name, which here is ZeroDivisionError; printing the variable $!
alone has the effect of displaying the error message contained by the Exception object, which here is “divided by 0.”
I am not generally keen on relying upon global variables, particularly when they have names as undescriptive as $!
. Fortunately, there is an alternative. You can associate a variable name with the exception by placing the “assoc operator” (=>
) after the class name of the exception and before the variable name:
exception2.rb
rescue Exception => exc
You can now use the variable name (here exc
) to refer to the Exception object:
puts( exc.class ) puts( exc )
Although it may seem pretty obvious that when you divide by zero, you are going to get a ZeroDivisionError exception, in real-world code there may be times when the type of exception is not so predictable. Let’s suppose, for instance, that you have a method that does a division based on two values supplied by a user:
def calc( val1, val2 ) return val1 / val2 end
This could potentially produce a variety of different exceptions. Obviously, if the second value entered by the user is 0, you will get a ZeroDivisionError.
exception_tree.rb
However, if the second value is a string, the exception will be a TypeError, whereas if the first value is a string, it will be a NoMethodError (because the String class does not define the “division operator,” which is /
). Here the rescue
block handles all possible exceptions:
multi_except.rb
def calc( val1, val2 ) begin result = val1 / val2 rescue Exception => e puts( e.class ) puts( e ) result = nil end return result end
You can test this by deliberately generating different error conditions:
calc( 20, 0 ) #=> ZeroDivisionError #=> divided by 0 calc( 20, "100" ) #=> TypeError #=> String can't be coerced into Fixnum calc( "100", 100 ) #=> NoMethodError #=> undefined method `/' for "100":String
Often it will be useful to take different actions for different exceptions. You can do that by adding multiple rescue
clauses. Each rescue
clause can handle multiple exception types, with the exception class names separated by commas. Here my calc
method handles TypeError and NoMethodError exceptions in one clause with a catchall Exception handler to deal with other exception types:
multi_except2.rb
def calc( val1, val2 ) begin result = val1 / val2 rescue TypeError, NoMethodError => e puts( e.class ) puts( e ) puts( "One of the values is not a number!" ) result = nil rescue Exception => e puts( e.class ) puts( e ) result = nil end return result end
This time, when a TypeError or NoMethodError is handled (but no other sort of error), my additional error message is displayed like this:
NoMethodError undefined method `/' for "100":String One of the values is not a number!
When handling multiple exception types, you should always put the rescue
clauses dealing with specific exceptions first and then follow these with rescue
clauses dealing with more generalized exceptions.
When a specific exception such as TypeError is handled, the begin..end
exception block exits so the flow of execution won’t “trickle down” to more generalized rescue
clauses. However, if you put a generalized exception-handling rescue
clause first, that will handle all exceptions, so any more specific clauses lower down will never execute.
If, for example, I had reversed the order of the rescue
clauses in my calc
method, placing the generalized Exception handler first, this would match all exception types so the clause for the specific TypeError and NoMethodError exceptions would never be run:
multi_except_err.rb
# This is incorrect... rescue Exception => e puts( e.class ) result = nil rescue TypeError, NoMethodError => e puts( e.class ) puts( e ) puts( "Oops! This message will never be displayed!" ) result = nil end calc( 20, 0 ) #=> ZeroDivisionError calc( 20, "100" ) #=> TypeError calc( "100", 100 ) #=> NoMethodError