Ruby Exception Handling

An exception is an unexpected event that occurs during program execution. For example,

puts 7 / 0

The above code causes an exception as it is not possible to divide a number by 0.

Other common exceptions include opening a missing file, getting invalid user input, etc.

The process of handling these types of errors in Ruby is known as exception handling.

In Ruby, we handle exceptions with the help of the begin...rescue statement. Here's how the begin and rescue blocks work:

  • begin - Contains code that may raise an exception.
  • rescue - Contains code that handles the exception.

Syntax for Exception Handling in Ruby

The basic syntax for exception handling in Ruby is given below:

begin
  # Code that may raise an exception
rescue
  # Code to handle exception
end

Here, we have placed the code that might generate an exception inside the begin block. Every begin block is followed by the rescue block.

The rescue block cannot be used without the begin block.


Example: Ruby Exception Handling

begin
  puts 7 / 0
rescue
  puts "Encountered a problem!"
end

# Output: Encountered a problem!

Here, we try to print the result of 7 / 0 inside the begin block. But dividing by 0 causes an exception (called ZeroDivisionError).

So, instead of crashing, the program goes to the rescue block and prints the message "Encountered a problem!"

Now, let's see what happens when we try to divide by a non-zero number:

begin
  puts 7 / 2
rescue
  puts "Encountered a problem!"
end

# Output: 3

Since the code inside the begin block causes no exceptions, the rescue block is skipped entirely.


Catching the Error Object

You can also capture the actual error using rescue => e, where the error is stored in the variable e. For example,

begin
  puts 7 / 0
rescue => e
  puts "An error occurred: #{e.message}"
end

# Output: An error occurred: divided by 0

Here, e catches the ZeroDivisionError in Ruby. Thus, e becomes an object of class ZeroDivisionError.

And, e.message gives a short description of what went wrong.

Since e is an object of ZeroDivisionError, e.message gives us "divided by 0".

Note: You can use other variable names to catch the error object. But we generally use e.


Handling Specific Errors

Instead of catching all errors, you can handle specific ones. For example, let's specifically catch the ZeroDivisionError we've been dealing with so far:

begin
  puts 7 / 0
rescue ZeroDivisionError
  puts "Cannot divide by zero."
end

# Output: Cannot divide by zero.

Here, rescue ZeroDivisionError only catches errors caused by arithmetic divisions by 0. It cannot catch any other exceptions. For example,

begin
  File.open("missing_file.txt")
rescue ZeroDivisionError
  puts "Cannot divide by zero."
end

Output

main.rb:2:in 'File#initialize': No such file or directory @ rb_sysopen - missing_file.txt (Errno::ENOENT)

Here, we've tried to open a file that doesn't exist. So, this raises an Errno::ENOENT exception.

But the rescue block doesn't catch this exception because it's only designed to catch the ZeroDivisionError exception.

To fix this, you can do one of three things:

  • Don't specify any particular exception in the rescue block.
  • Or, replace the rescue ZeroDivisionError with rescue Errno::ENOENT or rescue StandardError alongside the appropriate error message.
  • Or, use multiple rescue statements, where one includes Errno::ENOENT.

Note: It's always preferable to check for specific exceptions. So, if you know that a block of code might raise a "file not found" error, you must specifically check for the Errno::ENOENT exception.

If you know that code block might raise both ZeroDivisionError and Errno::ENOENT exceptions, then you must use multiple rescue statements to catch those exceptions.


Further Examples

Catch Errno::ENOENT exception without specifying it in the rescue block.

You can catch the Errno::ENOENT exception without explicitly specifying its type. Here are two ways to do it:

1. Without catching the error object.

begin
  File.open("missing_file.txt")
rescue
  puts "Cannot find the file!"
end

# Output: Cannot find the file!

2. By catching the error object.

begin
  File.open("missing_file.txt")
rescue => e
  puts e.message
end

# Output: No such file or directory @ rb_sysopen - missing_file.txt
Catch Errno::ENOENT exception by specifying it in the rescue block.

You can specify the Errno::ENOENT exception in your rescue block like this:

begin
  File.open("missing_file.txt")
rescue Errno::ENOENT
  puts "Cannot find the file!"
end

# Output: Cannot find the file!

Note: You can also use StandardError instead of Errno::ENOENT. However, it's not preferred because you already have the specific error class for this case, i.e., Errno::ENOENT.


Ruby Multiple rescue Statements

You can also use multiple rescue statements to prepare for many specific exceptions. For example,

begin
  File.open("missing_file.txt")
rescue ZeroDivisionError
  puts "Cannot divide by zero."
rescue Errno::ENOENT
  puts "Cannot find the file!"
rescue
  puts "Some unknown error occurred!"
end

# Output: Cannot find the file!

In this example:

  1. Ruby checks the first rescue. If the error isn't ZeroDivisionError, it moves to the next rescue.
  2. Since the file doesn't exist, Ruby matches Errno::ENOENT and prints "File not found."
  3. The final rescue block catches any other exception since no particular exception is specified there.

Using ensure to Always Run Code

There may be times when you want to run certain code no matter what, whether there's an error or not.

You can do this with an ensure block. For example,

begin
  print "Enter the denominator: "

  # Get integer input for num
  num = gets.chomp.to_i

  puts 7 / num

rescue => e
  puts "An error occurred: #{e.message}"

ensure
  puts "This is executed no matter what!"
end

Sample Output 1

Enter the denominator: 0
An error occurred: divided by 0
This is executed no matter what!

Sample Output 2

Enter the denominator: 2
3
This is executed no matter what!

As you can see, whether the code raises an exception or not, the statement inside the ensure block is always executed.

Tip: Remove the ensure block and see what happens.


Raising Your Own Errors

You can also create your own errors using the raise keyword. For example,

# Create a method with a custom error
def check_age(age)
  raise "You must be 18 or older" if age < 18
  puts "You are allowed!"
end

print "Enter your age: "

# Get integer input for age
age = gets.chomp.to_i

check_age(age)

Sample Output 1

Enter your age: 10
/tmp/uZyh9UYWkP/main.rb:2:in 'Object#check_age': You must be 18 or older (RuntimeError)

Sample Output 2

Enter your age: 21
You are allowed!

In this example, we raise an error manually when age is less than 18.

Note: Our code raises a RuntimeError by default. Instead, it's preferable to create custom error classes for handling such errors.


Creating Custom Error Classes

You can create your own error types by subclassing the StandardError class provided by Ruby.

# Create subclass AgeError from StandardError
class AgeError < StandardError; end

# Method that raises a custom error
def check_age(age)
  raise AgeError, "You must be 18 or older" if age < 18
  puts "You are allowed!"
end

# Handle the custom error using begin...rescue
begin
  print "Enter your age: "
  age = gets.chomp.to_i
  check_age(age)
rescue AgeError => e
  puts "Custom error: #{e.message}"
end

Sample Output 1

Enter your age: 10
ERROR!
Custom error: You must be 18 or older

Sample Output 2

Enter your age: 21
You are allowed!

Here, we've created a custom error class AgeError, which is derived from the StandardError superclass.

Then, we raise this error inside the check_age method using the following code:

# Code to raise custom error
raise AgeError, "You must be 18 or older" if age < 18

Finally, we handle this possible error using a begin...rescue statement.

Note: Custom error classes are useful in larger programs when you want to define different types of errors for different situations.


Frequently Asked Questions

What are some common Ruby exceptions?

Here are some common exceptions in Ruby:

Error Class Error Trigger
ZeroDivisionError When you divide a number by 0.
NameError When you use a variable that doesn't exist.
TypeError When you use the wrong type (e.g., add string to number).
ArgumentError When you call a method with the wrong number of arguments.
Errno::ENOENT When you try to open a file that doesn't exist.
What are the best practices for exception handling?

Here are some best practices for exception handling in Ruby:

  • Rescue only the exceptions you expect.
  • Avoid using a bare rescue, which can hide bugs.
  • Use ensure for cleanup (like closing files or releasing resources).
  • Use exception objects to log or display detailed error information.
  • Keep rescue blocks concise and focused on handling the error.
  • Raise meaningful custom errors when needed.
How to use the else statement in exception handling?

In Ruby, the else statement in exception handling is used to define a block of code that runs only if no exception occurs in the begin block.

Syntax

begin
  # Code that might raise an exception
rescue => e
  # Code that runs if an exception is raised
else
  # Code that runs only if no exception was raised
end

Example

begin
  print "Enter a number: "
  num = gets.chomp.to_i
  result = 7 / num
rescue ZeroDivisionError
  puts "You can't divide by zero!"
else
  puts "Result is #{result}"
end

Sample Output 1

Enter a number: 0
You can't divide by zero!

Sample Output 2

Enter a number: 2
Result is 3

Here's how this program works:

  • If the user enters 0, the rescue block runs and prints an error message.
  • If the user enters a non-zero number (like 2, -9, 567, etc.), the division succeeds and the else block runs.

Important Notes:

  • The else block is useful when you want to run code only after the begin block succeeds (and not when rescue is triggered).
  • It should appear after all rescue clauses and before any ensure block.

Syntax for else with ensure block.

begin
  # Code that might raise an exception
rescue => e1
  # Code that runs if an exception is raised
rescue => e2
  # Code that runs if another exception is raised
... ... ...
rescue => eN
  # Code that runs if some other exception is raised
else
  # Code that runs only if no exception occurs
ensure
  # Code that always runs, no matter what
end

As you can see, the else block is positioned:

  • After all rescue blocks.
  • Before the ensure block.
Did you find this article helpful?

Your path to become a builder.

Builders don’t just know how to code, they create solutions that matter. Escape tutorial hell and ship real projects.

Try Programiz PRO
  • Real-World Projects
  • On-Demand Learning
  • AI Mentor
  • Builder Community