academy

Rescuing exceptions in Ruby

Jeff Kreeftmeijer

Jeff Kreeftmeijer on

Rescuing exceptions in Ruby

A raised exception can be rescued to prevent it from crashing your application once it reaches the top of the call stack. In Ruby, we use the rescue keyword for that.

When rescuing an exception in Ruby, you can specify a specific error class that should be rescued from.

1begin
2  raise 'This exception will be rescued!'
3rescue StandardError => e
4  puts "Rescued: #{e.inspect}"
5end

Note: When using raise without specifying an exception class, Ruby will default to RuntimeError.

Besides specifying a single exception class to rescue, you can pass an array of exception classes to the rescue keyword. This will allow you to respond to multiple errors in the same way.

1begin
2  raise 'This exception will be rescued!'
3rescue StandardError, AnotherError => e
4  puts "Rescued: #{e.inspect}"
5end

Multiple rescue blocks can be used to handle different errors in different ways. This can be useful when working with a library that produces different exceptions for different scenarios.

1begin
2  raise 'This exception will be rescued!'
3rescue StandardError => e
4  puts "Rescued: #{e.inspect}"
5rescue AnotherError => e
6  puts "Rescued, but with a different block: #{e.inspect}"
7end

The exception hierarchy

Ruby’s exception hierarchy is used to differentiate between different types of errors, while giving you the ability to rescue from a group of errors without specifying all of them.

Although libraries can define their own exception subclasses, the list of built-in exception subclasses on Ruby 2.5 looks like this:

1- NoMemoryError
2- ScriptError
3    - LoadError
4    - NotImplementedError
5    - SyntaxError
6- SecurityError
7- SignalException
8    - Interrupt
9- StandardError (default for `rescue`)
10    - ArgumentError
11        - UncaughtThrowError
12    - EncodingError
13    - FiberError
14    - IOError
15        - EOFError
16    - IndexError
17        - KeyError
18        - StopIteration
19    - LocalJumpError
20    - NameError
21        - NoMethodError
22    - RangeError
23        - FloatDomainError
24    - RegexpError
25    - RuntimeError (default for `raise`)
26    - SystemCallError
27        - Errno::*
28    - ThreadError
29    - TypeError
30    - ZeroDivisionError
31- SystemExit
32- SystemStackError
33- fatal (impossible to rescue)

When omitting the exception class in a rescue block, StandardError is assumed. Because ArgumentError and NoMethodError are subclasses of StandardError, these are rescued from when they occur in the block.

A good example of how the exception hierarchy works is SystemCallError, which is a low-level platform-dependent exception class. It’s seen most often when reading or writing to files.

Ruby’s File.read method will raise an exception if it fails to read the file. That can happen because of a number of reasons, like the file not existing or the program not having the correct permissions to read it.

Since these problems are platform-dependent, Ruby can raise different exceptions depending on what kind of operating system is running on the machine. For low-level errors like this, Ruby implements a different list of Errno::*-exceptions for each platform.

All of these Errno::* exceptions are subclasses of SystemCallError. Although they’re platform-specific, they can still be used in a rescue block by rescuing from SystemCallError.

1begin
2  File.read("does/not/exist")
3rescue SystemCallError => e
4  puts "Rescued: #{e.inspect}"
5end

Swallowing exceptions

Usually, it’s best to be as specific as possible when rescuing exceptions, to prevent unintentionally swallowing exceptions.

1image = nil
2
3begin
4  File.read(image.filename)
5rescue
6  puts "File can't be read!"
7end

In this example, the image variable is nil, so it raises a NoMethodError when we try to call #filename on it (NoMethodError: undefined method `filename' for nil:NilClass). Because every StandardError subclass is rescued from (including NoMethodError), the exception is swallowed and the “File can’t be read!”-message is printed. This hides a possible bug in the code.

Note: Although it’s possible, using the Exception superclass in a rescue block is highly discouraged.

Have any questions about raising or rescuing exceptions in Ruby? Please don't hesitate to let us know at @AppSignal. Of course, we’d love to know how you liked this article, or if you have another subject you’d like to know more about.

Share this article

RSS

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps