Throwing and Handling Errors

Working with Ruby's error messages and building your own.

Scroll down...

Content

Resources

Comments

In the real world, errors happen. You've encountered them hundreds of times already and will certainly encounter them thousands of times in the future. Building your application in a way that gracefully responds to unexpected situations with appropriate error messages is a necessary art.

In this lesson, we'll introduce you to the basics of how Ruby handles errors and how you can implement them in your own code.

What is an Error?

"Errors" and "Exceptions" are effectively the same thing in Ruby. Technically speaking, all of Ruby's errors are actually subclasses of the Exception class.

You hit an exception when the program encounters something which it cannot handle. Ruby then stops the program and starts backing up through the program's stack (the nested chain of method calls that got you to where you are), looking for something along the way that can handle what happened. If the error cannot be handled, it is displayed to you either in the browser or on the command line.

Begin / Rescue

Ruby provides a straightforward way of handling errors. You simply wrap the piece of code that might generate an error in a begin block and use rescue to specify which error(s) you are willing to handle from it. ensure will always run and retry allows you to try again from the beginning:

begin
    File.open("nonexistant.txt","r")
rescue SomeSpecificException
    # handle that exception
    retry    # to try the original code again
rescue SomeOtherException
    # handle that exception
else
    # handle any remaining exceptions
ensure
    # this code is always run
    puts "we found an error, and are now sad."
end

rescue will match the specific error or any of its superclasses and run the provided code if a match is found.

Code within the ensure is always run. This is optional.

retry goes back to the begin and tries again. This is optional. Careful -- this could cause an infinite loop if you're not careful!

Raising Errors

You can also raise your own errors using the raise method. The simplest way is to just provide a text string naming the error yourself:

> begin
>     raise "random exception found"
> rescue 
>     puts "caught the exception"
> end
caught the exception
#=> nil

Raise What?

raise lets you choose how specific you'd like to be:

raise   # raises default RuntimeException
raise "Error message"
raise ExceptionType, "Error Message"

Accessing the Raised Exception

rescue allows you to have access to the Exception object if you pass it in and you can use that to be more descriptive in your response, for instance by displaying a more specific error message or viewing the backtrace:

begin
    3/0
rescue Exception => e
    puts e.message  
    puts e.backtrace.inspect 
end
#divided by 0
#["(irb):5:in `/'", "(irb):5:in `irb_binding'" ... ]

When raising your own errors, you can raise an existing exception (like StandardError) or create your own.

Creating Your Own Exceptions

Exceptions are all descendants of Ruby's Exception class. The first layer of children for Exception provide you with some ample fodder for raising errors:

  • Interrupt
  • NoMemoryError
  • SignalException
  • ScriptError
  • StandardError -- this one contains the most commonly used application errors like RuntimeError and NoMethodError and ArgumentError.
  • SystemExit

You can see the full hierarchy here if you want to raise a specific exception

begin
    raise StandardError if some_condiditon
rescue StandardError
    puts "found a standard error around here!"
end

If you want to create your own, it needs to be a subclass of either Exception or one of its descendants. In practice, you should always inherit from StandardError or one of its descendants:

class BogusError < StandardError
end

# IRB
> raise BogusError.new
# BogusError: BogusError
#         from (irb):3

When To Use Exceptions

When you're building toy apps, you can rely on Ruby's pre-existing error handling to cover most cases. When you start building larger applications and encountering situations that should be fatal but aren't, you'll want to set up your own error handling.

Some good candidates for error handling include any interfaces with either the user or with other applications (whether locally or via web APIs). Interfaces are where the unexpected should be expected...

As a beginner, it can be tempting to simply ignore errors by wrapping troublesome bits of code in begin / rescue blocks but that's really not fixing the problem and is a bad habit to get into.

Catch and Throw for Flow Control

Perhaps more alarmingly, beginners often think of exceptions as great ways to control the flow of code. If you're nested 5 loops deep, it can be tempting to just throw an error and catch it at the top. BAD! Exceptions are ONLY for EXCEPTIONAL circumstances which should cause program failure.

There's undeniable power in the idea of using exceptions for flow control, though... so good thing Ruby provides the throw and catch methods which behave the same way. You don't often see this pattern in the wild because it can make the code more difficult to follow but it's good to know nonetheless.

How Throw and Catch Works

throw acts like a return statement except it only jumps you as far as the catch which wraps it. It can be helpful for breaking out of nested loops when otherwise a break would be insufficient.

Simply wrap your code in a catch block and then throw yourself out of it when you're ready. Make sure they take matching symbols so Ruby knows which catch to send your throw to:

def floor_sweeping
  floor = [["blank", "blank", "blank"],
           ["sand", "blank", "blank"],
           ["blank", "blank", "blank"]]
  dirt_type = nil
  catch(:swept) do         # <<<<<<<
    floor.each do |row|
      row.each do |tile|
        if tile == "sand"
          dirt_type = tile
          throw(:swept)    # <<<<<<<
        end
      end
    end
  end
  puts dirt_type
end

# IRB
> floor_sweeping
#=> sand

You can also pass variables into throw and catch will then return that variable:

> exposed_secret = catch(:var) do
>   secret = "bananas!"
>   throw(:var, secret)
> end
#=> "bananas!"


Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

Sorry, comments aren't active just yet!

Next Lesson: Debugging Tips