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.
begin
raise 'This exception will be rescued!'
rescue StandardError => e
puts "Rescued: #{e.inspect}"
end
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.
begin
raise 'This exception will be rescued!'
rescue StandardError, AnotherError => e
puts "Rescued: #{e.inspect}"
end
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.
begin
raise 'This exception will be rescued!'
rescue StandardError => e
puts "Rescued: #{e.inspect}"
rescue AnotherError => e
puts "Rescued, but with a different block: #{e.inspect}"
end
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:
- NoMemoryError
- ScriptError
- LoadError
- NotImplementedError
- SyntaxError
- SecurityError
- SignalException
- Interrupt
- StandardError (default for `rescue`)
- ArgumentError
- UncaughtThrowError
- EncodingError
- FiberError
- IOError
- EOFError
- IndexError
- KeyError
- StopIteration
- LocalJumpError
- NameError
- NoMethodError
- RangeError
- FloatDomainError
- RegexpError
- RuntimeError (default for `raise`)
- SystemCallError
- Errno::*
- ThreadError
- TypeError
- ZeroDivisionError
- SystemExit
- SystemStackError
- 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
.
begin
File.read("does/not/exist")
rescue SystemCallError => e
puts "Rescued: #{e.inspect}"
end
Swallowing exceptions
Usually, it’s best to be as specific as possible when rescuing exceptions, to prevent unintentionally swallowing exceptions.
image = nil
begin
File.read(image.filename)
rescue
puts "File can't be read!"
end
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.
A common problem in Ruby is that you get error messages, which in technical terms we call “exceptions”.
These exceptions can be expected, like a file that may be available sometimes but missing in others, or an API that is only available temporarily due to some restrictions, or they can be unexpected.
Today you’ll learn to manage expected errors.
How?
Well, let me introduce you to “begin” & “rescue” in Ruby, two important keywords used for handling error conditions.
How do they work?
First, you need to understand something.
Your Ruby programs can trigger an error at multiple points while they’re running.
Examples include:
- Trying to read a non-existing file.
- Dividing a number by zero.
- A web server you’re working with has an outdated SSL certificate.
When an error happens… Ruby doesn’t crash right away!
You get a chance to recover from the error. We call this “exception handling”.
Ruby gives you a few keywords to implement error recovery in your code. These keywords are begin
& rescue
.
Let’s discover how to use them!
How to Handle Ruby Exceptions
How do you handle these exceptions?
You can wrap the code that raises the exception with a begin
/ rescue
block.
Here’s how it works…
The first section (begin
), has the code that you’re going to run & that may raise an exception.
Example:
begin IO.sysopen('/dev/null') rescue # ... end
Here we’re trying to open a file with sysopen
. An exception is raised if we can’t open the file.
This is the perfect time to use the rescue
keyword!
Using this keyword you can say what you want to happen when an exception is raised. So the failure mode is under your control.
Example:
begin IO.sysopen('/dev/null') rescue puts "Can't open IO device." end
You want to log this error & maybe provide some kind of default value.
Don’t. Ignore. Errors.
Rescuing Multiple Exceptions
You need to know that rescue
takes an optional argument.
What is this argument?
This argument is the exception class that you want to rescue from.
It depends on what code you’re running.
For IO
:
- This may be
Errno::ENOENT
for a missing file - Or
Errno::EACCES
for a permission error
The best part?
You can handle multiple exceptions in the same begin/rescue block.
Like this:
begin IO.sysopen('/dev/null') rescue Errno::ENOENT puts "File not found." rescue Errno::EACCES puts "Insufficient permissions, not allowed to open file." end
If you want to have the same action happen for multiple exceptions…
You can do this:
begin IO.sysopen('/dev/null') rescue Errno::ENOENT, Errno::EACCES puts "There was an error opening the file." end
Let’s keep learning!
How to Rescue Exceptions Inside Blocks & Methods
You don’t always have to use the begin
keyword.
There are cases where you can leave it out.
Where?
Inside methods & blocks.
Example:
def get_null_device IO.sysopen('/dev/null') rescue Errno::ENOENT puts "Can't open IO device." end
The method definition itself does the work of begin
, so you can omit it.
You can also do this with blocks.
Example:
["a.txt", "b.txt", "c.txt"].map do |f| IO.sysopen(f) rescue Errno::ENOENT puts "Can't open IO device: #{f}." end
Now, there is one more way to use the rescue
keyword without begin
.
Let’s see how that works.
Understanding Inline Rescue & Why Is It Dangerous
You can use rescue
inline.
In a few rare scenarios, you may find this form of exception handling useful.
Here’s an example:
["a.txt", "b.txt", "c.txt"].select { |f| File.open(f) rescue nil }.map(&:size)
This allows you to open only the files that exist & ignore those that don’t.
As a result, you get the size for the existing files.
Without exceptions being raised.
Why do this?
Well, it lets you keep your code all in one line.
That’s about it.
There’s a “hidden danger” when using this form of rescue
because you’re getting all exceptions descending from StandardError
.
Which are most exceptions.
Why is that NOT good?
Because it’s best to only handle specific exceptions, instead of a broad selection of them.
This avoids hiding errors from yourself!
Hidden errors can lead to all kinds of strange behavior & hard-to-debug issues.
Summary
You’ve learned about errors in Ruby, basic exception handling & the rescue
/ begin
keywords.
Please share this article if you found it helpful 🙂
Thanks for reading!
In the Ruby programming language, exceptions are a common feature. About 30 different exception subclasses are defined in the Ruby standard library, some of which have their subclasses. Ruby’s exception mechanism is quite strong, yet it is frequently misused.
This article will go over how to use rescue exceptions in ruby and how to handle them.
Here’s how it is done:
- What is an Exception?
- What is Exception Handling?
- When to Handle an Exception?
- When do Exceptions Occur?
- Exception handling in Ruby
- Be Specific About Your Rescue
- The begin-rescue
What is an Exception?
A program error condition is represented by an exception. Exceptions are a mechanism for suspending a program’s execution. They work in the same way as «break» in that they cause the instruction pointer to jump to a different location. The location, unlike break, could be another tier of the program stack. Ruby programs crash when unhandled exceptions occur.
What is Exception Handling?
Software systems are typically prone to errors. Exceptions are more likely to occur in systems that include human involvement because they attract errors on numerous levels.
Syntactical errors, network errors, form input errors, invalid authentication errors, and so on are all examples of errors. These can damage user experience and potentially emerge as security loopholes, allowing attackers to compromise the system if not addressed.
Exception handling prevents system failures and saves the user from annoying error messages.
When an error condition occurs, the programming language usually compels the program to terminate, and control of the program is lost. Exception handling allows you to maintain control and respond to what occurs after an error has occurred.
This offers you some flexibility to make changes, such as displaying friendly error messages, releasing resources, or quickly redirecting the user to a relevant website.
Exception handling assists you in cleaning up the mess without making it appear such. It is the process of tolerating error conditions and responding to them programmatically, resulting in the execution of a supposedly alternate but already planned code sequence.
Read about Exception Handling in PHP 8.
When to Handle an Exception?
Handling exceptions follows a simple rule: handle only those exceptions over which you have control. It’s simple to say, but it’s not always easy to do. We tend to want to save every single possible exception.
We commonly log a message and resume execution since we don’t know what to do with an exception. This frequently leads to the creation of unnecessary code to deal with the failures.
We should only handle exceptions when we have a reasonable chance of correcting the issue and allowing our application to continue running.
When creating code, we must consider three primary kinds of exceptional behavior: possible, probable, and inevitable.
- Possible Exceptions
Theoretically, exceptions are possible, but they are unlikely to occur in the system. When such exceptions occur, it usually means that the system is seriously broken. The issue is irreversible in this scenario, and we should not attempt to handle the exceptions. - Probable Exceptions
Probable exceptions could occur during the execution of the program, such as a REST call failure caused by a DNS issue. These are the issues we can foresee while developing our program, and in some situations, we can identify a solution. This is where we should concentrate the majority of our exception handling efforts. - Inevitable Exceptions
Inevitable exceptions will occur frequently. Allowing these exceptions is a typical practice. The proactive detection of the exceptional state and branching around it is a more successful method. Exceptions should not be utilized to control the flow of the program. If we can predict that an operation would result in an exceptional state, we should avoid it wherever possible.
When do Exceptions Occur?
When an exception occurs, take corrective action. Exception handling is about making our programs behave more civilized. Beginning, rescuing, and burying exceptions are anti-patterns that should be avoided.
However, there are situations when the only thing we can do is report an exception. The most basic form of this reporting is to print information to standard error, indicating that an exception has occurred and maybe providing instructions on how to resolve the problem.
More advanced systems use logging and APM technologies like Atatus to report concerns. APM tools streamline the process by centralizing issue reporting and monitoring and promptly identifying ways to improve performance and resolve hidden exceptions.
Atatus support Ruby applications to ensure that no errors are missed while deployments are pushed into production.
Exception handling in Ruby
Exception handling is performed in the majority of languages utilizing «try, throw, and catch» techniques. When the try block is reached, the compiler becomes aware of the possibility of an exception (thrown either by the user or by the language itself) and informs the catch block to handle it.
Read about PHP Exception Handling Using Try Catch.
Ruby chose alternative names for their standard exception handling system. There is a beginning, which is followed by errors that are raised and subsequently rescued. It comes to an end, like all wonderful things, unless you choose to retry.
begin
# something which might raise an exception
rescue
# code that deals with some exception
retry
# retry the code in the begin block
ensure
# ensure that this code always runs, no matter what
end
In Ruby, exception handling comprises-
- Begin — End block
- Raise
- Rescue
- Retry
- Ensure
The programming concepts of try, throw, catch, and finally are analogous to the begin-end block, raise, rescue, and ensure.
Be Specific About Your Rescue
What your rescue statement can handle should be specified. Use the most general class name possible if your rescue block can handle many erroneous conditions. This can be StandardError in some circumstances, although it’s most typically a subtree of the class hierarchy behind StandardError.
StandardError and all of its subtypes are captured by a basic rescue, which means it will catch any class raised that extends StandardError. This is troublesome. Only those exceptions should be rescued over which you have control. Other exceptions should be permitted to pass through your rescue statement.
If you use an APM tool like Atatus, you can collect every error that you can’t fix and are just causing unnecessary noise.
Every Ruby error is monitored using error tracking and captured with a full stack trace with the specific line of source code underlined to make error fixing easier and faster. To solve Ruby exceptions and errors, you can collect all the necessary information such as class, message, URL, request agent, version, and so on. You can also discover buggy APIs or third-party services by investigating API failure rates and application crashes.
The begin-rescue
Ruby’s exception handling starts with the begin-rescue block, similar to PHP’s try-catch. In short, the begin-rescue code block is used to handle raised exceptions without interrupting program execution. In other words, you can start executing a block of code while rescuing any raised exceptions.
#1 Rescuing Exceptions
Begin-rescue saves all instances of the StandardError class by default. This includes no method errors, type errors, runtime errors, as well as a custom error that is designed to be rescued within a Ruby application.
Simply enclose the designated section of code in a begin-rescue block to rescue every StandardError:
begin
# Raise an error here
rescue => e
# Handle the error here
end
When a StandardError exception is thrown in the begin block, an instance of it is provided to the rescue block as variable e.
#2 Rescuing Specific Exceptions
While rescuing every exception raised is great for simple implementations like generalizing API error responses, the best practice is to rescue for specific exceptions.
To accomplish this, update the above general begin-rescue block to specifically rescue StandardError exceptions:
begin
# Raise an error here
rescue StandardError => e
# Handle the error here
end
Although the difference may be minor, if you use a class name after the rescue command, only exceptions of that type will be rescued.
If we wanted to rescue all argument errors, for example, we could build our begin-rescue block as follows:
begin
# Raise an error here
rescue ArgumentError => e
# Handle the error here
end
But what if we need to save multiple exception types?
A begin-rescue block can have many rescues, similar to an if-elsif-else chain, which, when combined with a check for the StandardError class, allows you to logically adapt to any issues that may arise:
begin
# Raise an error here
rescue ArgumentError => e
# Handle the argument error
rescue TypeError => e
# Handle the type error
rescue => e
# Handle the other errors
end
#3 Rescuing All Exceptions
While it may be tempting to rescue every child of the Exception class, due to the structure of the Ruby exception hierarchy, this is typically considered bad practice. This is because, while all Ruby exceptions and errors are extensions of the Exception class, many of them are reserved for Ruby’s internal usage.
SignalException::Interrupt, for example, is used to signal that Ctrl-C has been detected during script execution.
The Interrupt exception would not work as expected if you rescued every child of the Exception class. However, if you wish to rescue every exception raised in Ruby, you may use the same begin-rescue block to specify rescue all Exception exceptions:
begin
#...
rescue Exception => e
#...
end
Use the above begin-rescue block sparingly and with caution because it will rescue any exception and error that is raised, from interruptions to syntax errors, and even memory errors.
#4 Rescuing Loops
The «rescue» keyword can also be used on loops. After all, in Ruby, loops are just statements.
The rescue syntax for the various loops is as follows:
while do
#... loop body
end rescue ...error handler...
begin
#... loop body
end while rescue ...error handler...
until do
#... loop body
end rescue ...error handler...
for in expression do
#... loop body
end rescue ...error handler...
When using a rescue on a loop, there are a few things to keep in mind. The rescue is carried out after the loop has been completed.
#5 Rescue Each
You might be asking whether «rescue» can be used with an «each» iterator. Yes, it is correct.
The following is the syntax:
.each {} rescue ...error handler...
Conclusion
We learned that Ruby has different terminology for handling exceptions. Except for the retry function, the begin/raise/rescue paradigm is similar to the try/throw/catch paradigm used in most other programming languages.
In Ruby, however, throwing and catching allow you to get out of complex constructions quickly. We also learned how to extend the StandardError class to construct application-specific custom Exception classes.
Ruby’s rescue block is quite powerful. It’s far more user-friendly than error codes. Rescue enables you to build more robust solutions by giving you a simple approach to dealing with typical software problems.
Monitor Your Ruby Applications with
Atatus
Atatus keeps track of your Ruby application to give you a complete picture of your clients’ end-user experience. You can determine the source of delayed response times, database queries, and other issues by identifying backend performance bottlenecks for each API request.
To make bug fixing easier, every Ruby error is captured with a full stack trace and the specific line of source code marked. To assist you in resolving the Ruby error, look at the user activities, console logs, and all Ruby requests that occurred at the moment. Error and exception alerts can be sent by email, Slack, PagerDuty, or webhooks.
Try Atatus’s entire features free for 14 days.
TL;DR
Don’t rescue Exception => e
(and not re-raise the exception) — or you might drive off a bridge.
Let’s say you are in a car (running Ruby). You recently installed a new steering wheel with the over-the-air upgrade system (which uses eval
), but you didn’t know one of the programmers messed up on syntax.
You are on a bridge, and realize you are going a bit towards the railing, so you turn left.
def turn_left
self.turn left:
end
oops! That’s probably Not Good™, luckily, Ruby raises a SyntaxError
.
The car should stop immediately — right?
Nope.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
beep beep
Warning: Caught SyntaxError Exception.
Info: Logged Error — Continuing Process.
You notice something is wrong, and you slam on the emergency breaks (^C
: Interrupt
)
beep beep
Warning: Caught Interrupt Exception.
Info: Logged Error — Continuing Process.
Yeah — that didn’t help much. You’re pretty close to the rail, so you put the car in park (kill
ing: SignalException
).
beep beep
Warning: Caught SignalException Exception.
Info: Logged Error — Continuing Process.
At the last second, you pull out the keys (kill -9
), and the car stops, you slam forward into the steering wheel (the airbag can’t inflate because you didn’t gracefully stop the program — you terminated it), and the computer in the back of your car slams into the seat in front of it. A half-full can of Coke spills over the papers. The groceries in the back are crushed, and most are covered in egg yolk and milk. The car needs serious repair and cleaning. (Data Loss)
Hopefully you have insurance (Backups). Oh yeah — because the airbag didn’t inflate, you’re probably hurt (getting fired, etc).
But wait! There’s more reasons why you might want to use rescue Exception => e
!
Let’s say you’re that car, and you want to make sure the airbag inflates if the car is exceeding its safe stopping momentum.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Here’s the exception to the rule: You can catch Exception
only if you re-raise the exception. So, a better rule is to never swallow Exception
, and always re-raise the error.
But adding rescue is both easy to forget in a language like Ruby, and putting a rescue statement right before re-raising an issue feels a little non-DRY. And you do not want to forget the raise
statement. And if you do, good luck trying to find that error.
Thankfully, Ruby is awesome, you can just use the ensure
keyword, which makes sure the code runs. The ensure
keyword will run the code no matter what — if an exception is thrown, if one isn’t, the only exception being if the world ends (or other unlikely events).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Boom! And that code should run anyways. The only reason you should use rescue Exception => e
is if you need access to the exception, or if you only want code to run on an exception. And remember to re-raise the error. Every time.
Note: As @Niall pointed out, ensure always runs. This is good because sometimes your program can lie to you and not throw exceptions, even when issues occur. With critical tasks, like inflating airbags, you need to make sure it happens no matter what. Because of this, checking every time the car stops, whether an exception is thrown or not, is a good idea. Even though inflating airbags is a bit of an uncommon task in most programming contexts, this is actually pretty common with most cleanup tasks.
In Ruby, any time our code throws an error, our program will break. Now, this is often a good feature because errors tell us what our bug is and where in the code it’s located. However, sometimes we have some code that may throw an error, such as getting user input that isn’t what we expected. Let’s say we have a calculator program and a user tries to divide a number by 0.
loop do
print "Please enter a base number:"
x = gets
print "Please enter a divisor:"
y = gets
puts x.to_i * y.to_i
end
The code above will throw a ZeroDivisionError if y is equal to zero or a non-number string. (Strings converted to integers in ruby evaluate to 0). However, we don’t want our program to break when the user inputs 0. Instead, we want to let them know that dividing a number by 0 is impossible in math. So, we need to find a way to display a message to the user and keep our program going. We can accomplish this using a rescue in Ruby.
Rescue in Ruby
begin
# code to execute that may throw an error
rescue
# code that is executed if the above code throws an error
end
In our case, we could write the code like:
loop do
print "Please enter a base number:"
x = gets
print "Please enter a divisor:"
y = gets
puts y
begin
puts x.to_i / y.to_i
rescue
puts "Sorry, the divisor you entered is invalid. Please enter a non-zero number for the divisor."
end
end
If the user enters an invalid divisor, the error message is shown and the program continues to run. Now we can even be more specific if we know what errors we’re expecting.
Passing Arguments to Rescue
Rescue by default handles any StandardError, but we can a specific error as an argument. If we pass in an error like TypeError, that rescue block will only execute if the error thrown is a TypeError.
loop do
print "Please enter a base number:"
x = gets
print "Please enter a divisor:"
y = gets
puts y
begin
puts x.to_i / y.to_i
rescue ZeroDivisionError
puts "Sorry, the divisor you entered is invalid. Please enter a non-zero number for the divisor."
end
end
In this way, we can handle multiple errors differently in the same statement.
begin
# the code in question
rescue ZeroDivisionError
# execute code specific to this error
rescue TypeError
# so on
rescue ArgumentError
# and so forth
end
We can also operate on the error thrown as a variable.
begin
# the code in question
rescue => error
puts "A #{error.class} was thrown: #{error.message}"
end
Else and Ensure
In our begin-end block, we can also use else and ensure statements. The else block will run if no exception is thrown, and the ensure block will always run.
begin
# code in question
rescue
puts "Oops we have an error..."
else
puts "No errors here"
ensure
puts "I will always run - error or not!"
end
Raising Errors
Now that we know a bit about how to rescue our errors, what if we actually want to raise a custom error of our own? We can do this using raise in Ruby.
raise StandardError.new("custom error message")
If you have a program that needs a new type of error, you can also create one using class inheritance.
class MyCustomError < StandardError
end
if true
raise MyCustomError("my custom error message")
end
Questions
Feel free to let me know in the comments:
- Have you used begin/end blocks in your code before?
- Have you ever created a custom error for a program or application?
- What is the error message you dread the most?
- What is your favorite error message?
- Other comments/questions?