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
rescueblock. - Or, replace the
rescue ZeroDivisionErrorwithrescue Errno::ENOENTorrescue StandardErroralongside the appropriate error message. - Or, use multiple
rescuestatements, where one includesErrno::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
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
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:
- Ruby checks the first
rescue. If the error isn'tZeroDivisionError, it moves to the nextrescue. - Since the file doesn't exist, Ruby matches
Errno::ENOENTand prints"File not found." - The final
rescueblock 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
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. |
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
ensurefor cleanup (like closing files or releasing resources). - Use exception objects to log or display detailed error information.
- Keep
rescueblocks concise and focused on handling the error. - Raise meaningful custom errors when needed.
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
rescueblock runs and prints an error message. - If the user enters a non-zero number (like 2, -9, 567, etc.), the division succeeds and the
elseblock runs.
Important Notes:
- The
elseblock is useful when you want to run code only after thebeginblock succeeds (and not whenrescueis triggered). - It should appear after all
rescueclauses and before anyensureblock.
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
rescueblocks. - Before the
ensureblock.