Exception in Ruby
Usually indicating that something went wrong during program execution, an exception is an object that represents an uncommon or exceptional condition. Errors are another name for exceptions in Ruby. The software often crashes or exits if an exception is triggered and not handled explicitly, disrupting the regular flow of execution.
Because Ruby’s exception system is a control structure, raising an exception shifts control from the site of failure to designated handling code. Ruby handles exceptions with the rescue clause and signals them with the Kernel
method raise.
The Exception Hierarchy
Every exception object is an instance of either the Exception class or one of the numerous subclasses that it contains. It is critical to comprehend the hierarchy:
Exception
: The base of the hierarchy. Exceptions that derive directly from Exception (like SystemExit
or NoMemoryError
) usually indicate significant or virtual machine faults and are generally not detectable with plain rescue.
StandardError
: The majority of “normal” exceptions are subclasses of StandardError
, which programmers should expect and manage. TypeError
, RuntimeError
, and ArgumentError
are a few examples.
You can also read What Is The Command Line Crash Course Manipulation in Ruby
Raising Exceptions
An exception is raised using the Kernel#raise
method (or its counterpart, Kernel#fail
).
Code Example: Raising Exceptions
def hello(subject = "World") # <-- Assign a default value here
# The subject will be "World" if no argument is passed,
# so this validation check is now redundant but kept for clarity.
if subject.to_s.empty?
# This line only executes if someone explicitly passes "" or nil.
raise ArgumentError, "`subject` is missing"
end
puts "Hello #{subject}"
end
# 1. Calling with no argument (uses default "World")
hello
# Expected Output: Hello World
# 2. Calling with a specific argument
hello("Rubyist")
# Expected Output: Hello Rubyist
# 3. Calling with a blank argument (will still raise the custom error)
# hello("")
# => ArgumentError: `subject` is missing
Output
Hello World
Hello Rubyist
A new RuntimeError
is created and raised with that string as the message if raise is called with a single string parameter. The exception class and an optional message are given in order to raise a particular kind of exception:
Custom Exceptions
By building a class that is a descendant of StandardError
, you can establish a custom exception type.
# 1. Define the Custom Exception
class FileNotFound < StandardError
# No body needed; it inherits all functionality from StandardError
end
# 2. The File Reading Method with Custom Error Logic
def read_file(path)
# Short-circuit logic: If File.exist? is FALSE, the raise executes.
File.exist?(path) || raise(FileNotFound, "File '#{path}' not found")
# This line only runs if the File.exist? check passed (returned true).
return File.read(path)
end
# --- Setup for Demonstration ---
# Create a temporary file for the SUCCESS case
File.write("existing.txt", "Hello from the file!")
# --- Demonstration ---
puts "--- SUCCESS CASE ---"
begin
content = read_file("existing.txt")
puts "Successfully read content: \"#{content.strip}\""
rescue StandardError => e
# This rescue block should not be hit in the success case
puts "Caught an unexpected error: #{e.message}"
end
puts "\n--- FAILURE CASE ---"
# Use a begin/rescue block to gracefully catch the expected error
begin
read_file("missing.txt")
rescue FileNotFound => e
puts "Caught expected error: #{e.class}"
puts "Error Message: #{e.message}"
end
# Clean up the temporary file
File.delete("existing.txt")
Output
--- SUCCESS CASE ---
Successfully read content: "Hello from the file!"
--- FAILURE CASE ---
Caught expected error: FileNotFound
Error Message: File 'missing.txt' not found
You can also read Method Visibility In Ruby : Public, Private, And Protected
Handling Exceptions (///)
The begin/end block is used to catch exceptions, and assure, else, and rescue clauses are added as needed. You can skip the begin keyword if you specify these clauses immediately in a method (def), class, or module block.
Code Example: Basic Handling and Clauses
Where an error might occur, code is executed by a begin
block. In the event that the begin
block raises an exception, the code to execute is specified by the rescue
clause.
def divide(x, y)
begin
return x/y
rescue
# A bare rescue catches StandardError and descendants
puts "There was an error"
return nil
end
end
divide(10, 0) # ZeroDivisionError caught; prints "There was an error"
Output
There was an error
Key Clauses and Components:
Clause | Purpose | Execution Timing |
rescue | Executes the error handling code. | Executes only if an exception matching the specified type is raised in the begin block. |
else | Executes code if no exception is raised in the begin block. | Executes only if the begin block runs to completion normally. |
ensure | Contains code that always runs, regardless of normal completion, explicit return , or unhandled exceptions. | Executes before the result is returned, ensuring cleanup (like closing files) is performed. |
Code Example: Specific Error Handling
It is up to you to decide which exceptions to save. Errors can be checked from top to bottom by stacking multiple rescue
clauses.
def divide(x, y)
begin
z = x/y
rescue ZeroDivisionError
puts "Don't divide by zero!" # Handles x/0
return nil # Exit the method after handling the error
rescue TypeError
puts "Division only works on numbers!" # Handles x/"a"
return nil
rescue => e # Catch all remaining StandardError subclasses
puts "Caught unexpected error: #{e.class}"
return nil
else
puts "Successfully calculated result." # Runs if no exceptions are raised
return z
ensure
puts "This code ALWAYS runs." # Runs whether successful or failed
end
end
# --- Demonstration Calls ---
puts "\n--- Successful Call (x/y) ---"
result_success = divide(10, 2)
puts "Returned value: #{result_success}"
puts "\n--- ZeroDivisionError Call (x/0) ---"
result_zero = divide(10, 0)
puts "Returned value: #{result_zero}"
puts "\n--- TypeError Call (x/'a') ---"
result_type = divide(10, 'a')
puts "Returned value: #{result_type}"
puts "\n--- Unexpected Error (x/Object) ---"
# We'll use a class that doesn't define division for demonstration
class SomeObject; end
result_other = divide(10, SomeObject.new)
puts "Returned value: #{result_other}"
Output
--- Successful Call (x/y) ---
Successfully calculated result.
This code ALWAYS runs.
Returned value: 5
--- ZeroDivisionError Call (x/0) ---
Don't divide by zero!
This code ALWAYS runs.
Returned value:
--- TypeError Call (x/'a') ---
Division only works on numbers!
This code ALWAYS runs.
Returned value:
--- Unexpected Error (x/Object) ---
Division only works on numbers!
This code ALWAYS runs.
Returned value:
You can also read What Is Inheritance In Ruby With Practical Code Examples
Rerunning Code ()
When used in a rescue
clause, the retry statement starts the enclosing begin block over from the beginning. This is helpful for temporary malfunctions (such as problems with network connectivity).
Code Example
This example code is intended to fail first, “fix” the issue in the rescue
block, and then successfully try again:
def rescue_and_retry
error_fixed = false
begin
puts 'I am before the raise in the begin block.'
raise 'An error has occurred!' unless error_fixed # Raises the first time
puts 'I am after the raise in the begin block.'
rescue
puts 'An exception was thrown! Retrying...'
error_fixed = true # Fixes the condition
retry # Restarts the begin block
end
puts 'I am after the begin block.'
end
rescue_and_retry
# Output:
# I am before the raise in the begin block.
# An exception was thrown! Retrying...
# I am before the raise in the begin block.
# I am after the raise in the begin block.
# I am after the begin block.
Output
I am before the raise in the begin block.
An exception was thrown! Retrying...
I am before the raise in the begin block.
I am after the raise in the begin block.
I am after the begin block.
Exception Information
To obtain comprehensive details, you can capture the exception object after it has been retrieved.
- => error: Appends
=> variable_name
to the rescue statement to store the exception object. - message: Returns a string providing human-readable details about the error.
- backtrace: Returns an array of strings representing the call stack when the exception occurred.
- $!: The global variable
$!
(or$ERROR_INFO
ifrequire 'English'
is used) refers to the last exception object raised.
begin
# Attempt risky operation: Division by zero.
result = 10 / 0
rescue Exception => e
puts "Caught an exception!"
puts "------------------------"
puts "Error Message: #{e.message}"
puts "\nBacktrace:"
puts e.backtrace.inspect
end
Output
Caught an exception!
------------------------
Error Message: divided by 0
Backtrace:
["/tmp/Kf3jwYOfXa/main.rb:3:in 'Integer#/'", "/tmp/Kf3jwYOfXa/main.rb:3:in '<main>'"]
You can also read Applying Polymorphism In Ruby with Inheritance & Duck Typing