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!
Hi, I want to buy Ruby Deep Dive book but I am based in Japan 🇯🇵 is it possible to buy this book in English in japan
Hi.
Yes, the book is in English, doesn’t matter where you buy it from 🙂
Example with “.map(&:size)” will return sizes of existing file names strings, not files.