What are Ruby Exceptions?

Exceptions are Ruby's way of dealing with unexpected events.

If you've ever made a typo in your code, causing your program to crash with a message like SyntaxError or NoMethodError then you've seen exceptions in action.

When you raise an exception in Ruby, the world stops and your program starts to shut down. If nothing stops the process, your program will eventually exit with an error message.

Here's an example. In the code below, we try to divide by zero. This is impossible, so Ruby raises an exception called ZeroDivisionError. The program quits and prints an error message.

1 / 0
# Program crashes and outputs: "ZeroDivisionError: divided by 0"

Crashing programs tend to make our users angry. We normally want to stop this shutdown process and react to the error intelligently.

This is called "rescuing," "handling," or "catching" an exception. They all mean the same thing. This is how you do it in Ruby:

begin
  # Any exceptions in here... 
  1/0
rescue
  # ...will cause this code to run
  puts "Got an exception, but I'm responding intelligently!"
  do_something_intelligent()
end

# This program does not crash.
# Outputs: "Got an exception, but I'm responding intelligently!"

The exception still happens, but it doesn't cause the program to crash because it was "rescued." Instead of exiting, Ruby runs the code in the rescue block, which prints out a message.

This is nice, but it has one big limitation. It tells us "something went wrong," without letting us know what went wrong.

All of the information about what went wrong is going to be contained in an exception object.

» Exception Objects

Exception objects are normal Ruby objects. They hold all of the data about "what happened" for the exception you just rescued.

To get the exception object, use the rescue syntax below.

# Rescues all errors, and puts the exception object in `e`
rescue => e

# Rescues only ZeroDivisionError and puts the exception object in `e`
rescue ZeroDivisionError => e

In the second example above, ZeroDivisionError is the class of the object in e. All of the "types" of exceptions we've talked about are really just class names.

Exception objects also hold useful debug data. Let's take a look at the exception object for our ZeroDivisionError.

begin
  # Any exceptions in here... 
  1/0
rescue ZeroDivisionError => e
  puts "Exception Class: #{ e.class.name }"
  puts "Exception Message: #{ e.message }"
  puts "Exception Backtrace: #{ e.backtrace }"
end

# Outputs:
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
# Exception Backtrace: ...backtrace as an array...

Most exception objects will provide you with at least three pieces of data:

  1. The type of exception, given by the class name.
  2. An exception message
  3. A backtrace

» Raising Your Own Exceptions

So far we've only talked about rescuing exceptions. You can also trigger your own exceptions. This process is called "raising" and you do it by calling the raise method.

When you raise your own exceptions, you get to pick which type of exception to use. You also get to set the message.

Here's an example:

begin
  # raises an ArgumentError with the message "you messed up!"
  raise ArgumentError.new("You messed up!")
rescue ArgumentError => e  
  puts e.message
end

# Outputs: You messed up! 

As you can see, we're creating a new error object (an ArgumentError) with a custom message ("You messed up!") and passing it to the raise method.

This being Ruby, raise can be called in several ways:

# This is my favorite because it's so explicit
raise RuntimeError.new("You messed up!")

# ...produces the same result
raise RuntimeError, "You messed up!"

# ...produces the same result. But you can only raise 
# RuntimeErrors this way
raise "You messed up!"

» Making Custom Exceptions

Ruby's built-in exceptions are great, but they don't cover every possible use case.

What if you're building a user system and want to raise an exception when the user tries to access an off-limits part of the site? None of Ruby's standard exceptions fit, so your best bet is to create a new kind of exception.

To make a custom exception, just create a new class that inherits from StandardError.

class PermissionDeniedError < StandardError
end

raise PermissionDeniedError.new()

Because PermissionDeniedError is a class, you can add attributes and methods to it like you would with any other class. Let's add an attribute called "action":

class PermissionDeniedError < StandardError

  attr_reader :action

  def initialize(message, action)
    # Call the parent's constructor to set the message
    super(message)

    # Store the action in an instance variable
    @action = action
  end

end

# Then, when the user tries to delete something they don't
# have permission to delete, you might do something like this:
raise PermissionDeniedError.new("Permission Denied", :delete)

» The Class Hierarchy

We just made a custom exception by subclassing StandardError, which itself subclasses Exception.

In fact, if you look at the class hierarchy of any exception in Ruby, you'll find it eventually leads back to Exception. Here, I'll prove it to you. These are most of Ruby's built-in exceptions, displayed hierarchically:

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit

You don't have to memorize all of these, of course. I'm showing them to you because this idea of hierarchy is very important. Here's why:

Rescuing errors of a specific class also rescues errors of its child classes.

For example, when you rescue StandardError, you not only rescue exceptions with class StandardError but those of its children as well. If you look at the chart, you'll see this includes ArgumentError, IOError, and many more.

Because all exceptions inherit from Exception you can rescue via rescue Exception. This, however, would be a very bad idea.

» Rescuing All Exceptions (The Wrong Way)

Here is some code you never want to submit to a code review:

# Don't do this 
begin
  do_something()
rescue Exception => e
  ...
end

The code above will rescue every exception. Don't do it! It'll break your program in weird ways.

That's because Ruby uses exceptions for things other than errors. It also uses them to handle messages from the operating system called "Signals." If you've ever pressed "ctrl-c" to exit a program, you've used a signal. By suppressing all exceptions, you also suppress those signals.

There are also some kinds of exceptions — like syntax errors — that really should cause your program to crash. If you suppress them, you'll never know when you make typos or other mistakes.

» Exceptions vs. Errors

To recap, an exception usually cannot or should not be recovered from. For example, SystemExit is raised by Ruby's exit method to initiate the termination of the Ruby program.

An error, on the other hand, is an exception which was caused by the Ruby application itself (usually originating somewhere in your code).

All errors inherit from StandardError (which itself inherits from Exception), while exceptions inherit directly from Exception.

Remember: Rescue errors, not exceptions.

» Rescuing All Errors (The Right Way)

Go back and look at the class hierarchy chart and you'll see that all of the errors you'd want to rescue are children of StandardError.

That means that if you want to rescue "all errors" you should rescue StandardError.

begin
  do_something()
rescue StandardError => e
  # Only your app's exceptions are swallowed. Things like SyntaxError are left alone. 
end

In fact, if you don't specify an exception class, Ruby assumes you mean StandardError.

begin
  do_something()
rescue => e
  # This is the same as rescuing StandardError
end

That said, before you jump to rescue => e as a catchall, do you really need to rescue all errors? Read the next section for a better way.

» Inline Rescue Statements

We'd be remiss if we didn't mention one of the more pernicious uses of rescue in Ruby: the inline rescue. You may have seen (or written) some code like this:

name = user.name rescue 'Anonymous'

Using rescue on a single line is Ruby shorthand for rescue => e, and therefore it rescues all instances of StandardError. While it may be nicer to read, this is a bad idea because there is no indication that an error occurred at all; don't do it, it's not worth the trouble!

» Rescuing Specific Errors (The Best Way)

Now that you know how to rescue all errors, you should know that it's usually a bad idea, a code smell, considered harmful, etc.

Blanked rescues are usually a sign that you were too lazy to figure out which specific exceptions needed to be rescued. And they will almost always come back to haunt you.

So take the time and do it right. Rescue specific exceptions.

begin
  do_something()
rescue Errno::ETIMEDOUT => e
  # This will only rescue Errno::ETIMEDOUT exceptions
end

You can even rescue multiple kinds of exceptions in the same rescue block, so no excuses. :)

begin
  do_something()
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
  # This will rescue both Errno::ETIMEDOUT and Errno::ECONNREFUSED exceptions
end