Error Handling with begin/rescue
Errors happen. Your code might try to divide by zero, open a file that doesn’t exist, or receive unexpected data from a user. Without error handling, any exception crashes your program. With error handling, you can recover gracefully and provide useful feedback.
In this tutorial, you’ll learn how Ruby’s begin, rescue, ensure, and raise keywords work together to handle errors elegantly.
The Basic Pattern: begin and rescue
Ruby provides a way to catch and handle exceptions using begin and rescue:
begin
# Code that might raise an error
result = 10 / 0
rescue
# Code that runs when an error occurs
puts "Something went wrong!"
end
When Ruby encounters an error inside the begin block, it immediately jumps to the rescue block. The 10 / 0 raises a ZeroDivisionError, which triggers the rescue block.
Why This Matters
Without rescue, your program crashes:
# Without begin/rescue - program crashes
result = 10 / 0
# => ZeroDivisionError: divided by 0
With rescue, you control what happens:
# With begin/rescue - graceful handling
begin
result = 10 / 0
rescue ZeroDivisionError
puts "Cannot divide by zero!"
result = 0 # Provide a safe default
end
puts result # => 0
Handling Specific Exception Types
Ruby has many exception types. Catching all exceptions with a bare rescue works but isn’t specific. It’s better to handle specific exception types:
begin
file = File.open("nonexistent.txt")
content = file.read
rescue Errno::ENOENT
puts "File not found!"
rescue Errno::EACCES
puts "Permission denied!"
end
This pattern handles different errors differently. Errno::ENOENT means “error, no entity” — the file doesn’t exist. Errno::EACCES means access denied.
Common Exception Types
| Exception | When It Occurs |
|---|---|
StandardError | Most user-code errors inherit from this |
ArgumentError | Wrong number of arguments |
NameError | Undefined variable or method |
TypeError | Wrong type used in operation |
ZeroDivisionError | Dividing by zero |
NoMethodError | Calling undefined method |
Catch StandardError for most user-written code errors. Avoid catching all exceptions (rescue Exception or rescue StandardError) unless you truly need to.
Storing the Exception
Ruby stores the exception object in a variable you can access:
begin
File.open("missing.txt")
rescue Errno::ENOENT => e
puts "Error: #{e.message}"
puts "Class: #{e.class}"
end
The => e syntax captures the exception object. You can then access:
e.message— the error messagee.class— the exception typee.backtrace— where the error occurred
Using ensure: Code That Always Runs
Sometimes you need code that runs whether or not an error occurs — like closing a file or releasing a resource. That’s what ensure is for:
file = File.open("data.txt", "w")
begin
file.write("Hello")
raise "Something went wrong!"
rescue
puts "Error occurred: #{$!}"
ensure
file.close # Always runs
end
The ensure block runs whether:
- The code succeeds
- An exception is raised and rescued
- An exception is raised and not rescued
Real-World Example: Database Connection
def fetch_users
db = Database.connect
begin
db.query("SELECT * FROM users")
rescue DatabaseError => e
puts "Database error: #{e.message}"
[]
ensure
db.close if db
end
end
This pattern ensures the database connection always closes, even when errors occur.
Using retry to Try Again
Sometimes an error is temporary — like a network timeout. You can use retry to run the begin block again:
begin
response = HTTP.get("https://api.example.com/data")
rescue Timeout::Error
puts "Request timed out, retrying..."
retry # Runs the begin block again
end
Be careful with retry: always add a limit to prevent infinite loops:
attempt = 0
begin
attempt += 1
response = HTTP.get("https://api.example.com/data")
rescue Timeout::Error
if attempt < 3
puts "Retry ##{attempt}"
retry
else
puts "Failed after 3 attempts"
end
end
Raising Your Own Exceptions
You don’t have to wait for Ruby to raise exceptions. Use raise to trigger your own:
def withdraw(amount)
raise ArgumentError, "Amount must be positive" if amount <= 0
raise InsufficientFundsError, "Not enough money" if amount > balance
# ... process withdrawal
end
You can raise any exception class. Ruby includes built-in ones, or create your own:
class MyCustomError < StandardError
end
raise MyCustomError, "Something went wrong!"
When to Use Error Handling
Use error handling when:
- External resources might be unavailable (files, networks, databases)
- User input might be invalid
- Operations might fail for reasons outside your control
Avoid using error handling for:
- Expected conditions that are normal (check for
nilfirst) - Flow control (use
if/elseinstead) - Every possible problem (keep it simple)
Summary
Ruby’s error handling uses begin to mark code that might fail, rescue to catch and handle specific errors, ensure for cleanup code that always runs, and raise to trigger your own exceptions.
Key points to remember:
- Always rescue specific exception types, not all exceptions
- Use
ensurefor cleanup code that must run - Add retry limits to prevent infinite loops
- Handle errors at the appropriate level of your program
Next Steps
Now that you understand error handling, continue with the next tutorial in the ruby-fundamentals series: “File I/O in Ruby” where you’ll learn how to read and write files while handling errors gracefully.