Contents
Exceptions
Ruby Exceptions
In programming, predicting errors and handling them effectively is crucial. Exceptions are errors that occur during runtime and disrupt the normal flow of a program. Handling exceptions properly ensures a program continues to operate even in unexpected situations.
What is an Exception?
An exception is an unexpected event that occurs during the execution of a program, disrupting its normal flow. It provides a way to handle error scenarios without stopping the entire program.
Errors vs. Exceptions
Errors:
- Unexpected issues that arise during program execution.
- Cannot be handled directly.
- All errors are considered exceptions.
Exceptions:
- Unexpected events during runtime.
- Can be managed using
begin-rescue
blocks. - Not all exceptions are errors.
Traditional Exception Handling Approach
In the traditional approach, errors were managed using return codes. Methods would return specific values indicating failure, which would be propagated through calling routines until a function took responsibility. This made error management complex and hard to maintain.
Ruby addresses this issue by using an exception class that packages information about errors into an object. This object is passed through the calling stack until appropriate code is found to handle the error.
Exception Class & Its Hierarchy
Ruby has a built-in hierarchy of exception classes. Most exceptions are subclasses of StandardError
, which represents general errors in Ruby programs. More serious exceptions fall under other classes. Users can also create their own exceptions by subclassing StandardError
or its descendants. Every exception object contains a message and a stack trace.
Example of an Exception
# Ruby program to illustrate an exception
# defining two integer values
num1 = 14
num2 = 0
# attempting to divide by zero
result = num1 / num2
puts "The result is: #{result}"
Runtime Error:
source_file.rb:6:in `/': divided by 0 (ZeroDivisionError)
from source_file.rb:6:in `'
Explanation
In the example above, dividing 14 by 0 triggers a ZeroDivisionError
.
Creating User-Defined Exceptions
Ruby uses the raise
method to create exceptions, which become instances of the Exception
class or its subclasses. The rescue
clause is used to handle these exceptions.
Example of User-Defined Exception
# Ruby program to create a user-defined exception
# defining a method
def raise_exception
puts 'This is before the exception is raised!'
# using raise to create an exception
raise 'Custom Exception Raised'
puts 'This line will not be executed.'
end
# calling the method
raise_exception
Output:
This is before the exception is raised!
source_file.rb:6:in `raise_exception': Custom Exception Raised (RuntimeError)
from source_file.rb:10:in `'
Handling Exceptions with rescue
You can handle exceptions in Ruby using begin-rescue
blocks. This structure allows the program to continue running after handling an exception.
Example of Handling Exception
# Ruby program to create and handle a user-defined exception
# defining a method
def raise_and_rescue
begin
puts 'This is before the exception is raised!'
# using raise to create an exception
raise 'Custom Exception Raised!'
puts 'This line will not be executed.'
# using rescue to handle the exception
rescue
puts 'Exception handled!'
end
puts 'Outside of the begin block!'
end
# calling the method
raise_and_rescue
Output:
This is before the exception is raised!
Exception handled!
Outside of the begin block!
Ruby Exception Handling
Exception handling in Ruby provides a way to manage unexpected events or errors that occur during the execution of a program. These errors can disrupt the normal flow of a program and are managed using various statements like begin
, rescue
, raise
, ensure
, and more. Ruby also provides an Exception
class, which has different methods to handle these errors.
Syntax of Exception Handling in Ruby
The code block where an exception might be raised is enclosed in a begin
and end
block. The rescue
clause is used to manage the exception.
Basic Syntax:
begin
# Code where an exception might be raised
rescue
# Code to handle the exception
end
Example of Exception Handling
# Ruby program to handle an exception
# defining a method
def raise_and_rescue
begin
puts 'This is before an exception occurs!'
# raising an exception
raise 'An Exception Occurred!'
puts 'This will not be printed'
rescue
puts 'Exception handled successfully!'
end
puts 'Outside the begin block!'
end
# calling the method
raise_and_rescue
Output:
This is before an exception occurs!
Exception handled successfully!
Outside the begin block!
Explanation: In this example, an exception is raised in the begin
block using raise
, which interrupts the program’s flow. The rescue
block then catches and handles the exception, allowing the program to continue executing.
Note
- You can use multiple
rescue
clauses to handle different exceptions. If an exception is not handled by the firstrescue
, the next one is tried. If norescue
matches, or if an exception occurs outside thebegin
block, Ruby searches up the call stack for an exception handler.
Statements Used in Exception Handling
1. retry
Statement: The retry
statement re-executes the code from the beginning of the begin
block after catching an exception.
Syntax:
begin
# Code where an exception might be raised
rescue
# Code to handle the exception
retry
end
Note: Be cautious while using retry
as it can lead to an infinite loop if not properly managed.
2. raise
Statement: The raise
statement is used to raise an exception in Ruby.
Syntax:
raise
: Re-raises the current exception.raise "Error Message"
: Creates aRuntimeError
with the given message.raise ExceptionType, "Error Message"
: Creates an exception of the specified type with the given message.
Example:
# Ruby program demonstrating the use of raise statement
begin
puts 'This is before an exception occurs!'
# Raising an exception
raise 'An Exception Occurred!'
puts 'This will not be printed'
end
Output:
This is before an exception occurs!
An Exception Occurred!
3. ensure
Statement: The ensure
block always executes regardless of whether an exception is raised or rescued. It is placed after the rescue
block.
Syntax:
begin
# Code where an exception might be raised
rescue
# Code to handle the exception
ensure
# Code that always executes
end
Output:
# Ruby program demonstrating the use of the ensure statement
begin
# Raising an exception
raise 'An Exception Occurred!'
puts 'This will not be printed'
rescue
puts 'Exception handled!'
ensure
puts 'Ensure block executed'
end
Output:
Exception handled!
Ensure block executed
4. else
Statement: The else
block executes only if no exception is raised in the begin
block. It is placed between the rescue
and ensure
blocks.
Syntax:
begin
# Code where an exception might be raised
rescue
# Code to handle the exception
else
# Code that executes if no exception is raised
ensure
# Code that always executes
end
Example:
# Ruby program demonstrating the use of the else statement
begin
puts 'No exception is raised here'
rescue
puts 'Exception handled!'
else
puts 'Else block executed because no exception was raised'
ensure
puts 'Ensure block executed'
end
Output:
No exception is raised here
Else block executed because no exception was raised
Ensure block executed
5. catch
and throw
Statements:catch
and throw
provide a lightweight mechanism for managing errors and jumps in Ruby. The catch
block works normally until it encounters a throw
. When a throw
is encountered, Ruby looks for the matching catch
block with the same label.
Syntax:
catch :label_name do
# Code block
throw :label_name if condition
end
Example:
# Ruby program to illustrate Class Variables
class Library
# Class variable
@@book_count = 0
def initialize(title)
# Instance variable
@title = title
end
def display_title
puts "Book Title: #{@title}"
end
def add_book
# Increment the class variable
@@book_count += 1
puts "Total books: #@@book_count"
end
end
# Creating objects
book1 = Library.new("The Art of Ruby")
book2 = Library.new("Ruby on Rails Guide")
# Calling methods
book1.display_title
book1.add_book
book2.display_title
book2.add_book
Input:
Enter a number: 1
Output:
1
Input:
Enter a number: !
Output:
nil
Catch and Throw Exception In Ruby
In Ruby, an exception is an object that belongs to the Exception class or one of its subclasses. An exception occurs when a program reaches an undefined or unexpected state. At this point, Ruby doesn’t know how to proceed, so it raises an exception. This can happen automatically or be done manually by the programmer.
The catch and throw keywords in Ruby provide a way to handle exceptions, similar to how raise and rescue work. The throw keyword generates an exception, and when it is encountered, the program’s control flow jumps to the corresponding catch statement.
Syntax
catch :label_name do
# Code block that runs until a throw is encountered
throw :label_name if condition
# This block will not execute if throw is encountered
end
The catch
block is used to jump out of nested code, and it will continue executing normally until a throw
statement is encountered.
Example: Basic catch
and throw
# Ruby Program using Catch and Throw for Exception Handling
result = catch(:divide) do
# Code block similar to 'begin'
number = rand(2)
throw :divide if number == 0
number # set result = number if no exception is thrown
end
puts result
Output: If the random number is 0
, the output will be empty since the throw :divide
statement will execute and jump to the catch
block. If the random number is 1
, the output will be:
1
Explanation: If the random number is 0
, throw :divide
is executed, and nothing is returned to the catch
block, resulting in an empty output. If the number is 1
, the exception is not thrown, and the result
variable is set to 1
.
Example:
In the previous example, when the exception was thrown, the variable result
was set to nil
. We can change this behavior by providing a default value to the throw
keyword.
# Ruby Program using Catch and Throw with a Default Value
result = catch(:divide) do
# Code block similar to 'begin'
number = rand(2)
throw :divide, 10 if number == 0
number # set result = number if no exception is thrown
end
puts result
Output:
1
Exception Handling in Ruby
In Ruby, a literal is any constant value that can be assigned to a variable. We use literals whenever we type an object directly into the code. Ruby literals are similar to those in other programming languages, with some differences in syntax and functionality.
Types of Ruby Literals
Ruby supports various types of literals:
1. Booleans and nil
2. Numbers
3. Strings
4. Symbols
5. Ranges
6. Arrays
7. Hashes
8. Regular Expressions
1. Booleans and nil: Booleans are constants that represent the truth values true
and false
. nil
is another constant that represents an “unknown” or “empty” value, and it behaves similarly to false
in conditional expressions.
Example:
raise exception_type, "exception message" if condition
Output:
true
false
false
When a raise
statement is executed, it invokes the rescue
block. By default, raise
raises a RuntimeError
.
Example 1: Using raise
Statement
# Ruby program to demonstrate the use of raise statement
begin
puts 'This is Before Exception Arises!'
# Using raise to create an exception
raise 'Exception Created!'
puts 'After Exception'
end
Output:
This is Before Exception Arises!
Exception Created!
Ruby Exception Handling in Threads
Threads in Ruby can also encounter exceptions. By default, only exceptions that arise in the main thread are handled, while exceptions in other threads cause those threads to terminate. The behavior of threads when an exception arises is controlled by the abort_on_exception
method, which is set to false
by default.
If abort_on_exception
is false
, an unhandled exception will abort the current thread but allow other threads to continue running. The setting can be changed by using Thread.abort_on_exception = true
or by setting $DEBUG
to true
. Additionally, Ruby provides the ::handle_interrupt
method for handling exceptions asynchronously within threads.
Example 1: Exception in Threads
# Ruby program to demonstrate exception handling in threads
#!/usr/bin/ruby
threads = []
4.times do |value|
threads << Thread.new(value) do |i|
# Raise an error when i equals 2
raise "An error occurred!" if i == 2
print "#{i}\n"
end
end
threads.each { |t| t.join }
Output:
0
3
1
main.rb:11:in `block (2 levels) in ': An error occurred! (RuntimeError)
Note: The Thread.join
method waits for a specific thread to finish. When a Ruby program ends, all threads are terminated, regardless of their states.
Saving Exceptions in Threads
In the following example, we handle exceptions within threads to prevent program termination and continue processing other threads.
Example 2: Handling Exceptions in Threads
# Ruby program to handle exceptions in threads
#!/usr/bin/ruby
threads = []
5.times do |value|
threads << Thread.new(value) do |i|
raise "An error occurred!" if i == 3
print "#{i}\n"
end
end
threads.each do |t|
begin
t.join
rescue RuntimeError => e
puts "Exception caught: #{e.message}"
end
end
Output:
0
1
4
2
Exception caught: An error occurred!
Using abort_on_exception
Setting abort_on_exception
to true
will terminate the entire program as soon as an exception occurs in any thread. Once the thread with the exception is terminated, no further output will be generated.
Example 3: Using abort_on_exception
# Ruby program to demonstrate thread termination on exception
#!/usr/bin/ruby
# Set abort_on_exception to true
Thread.abort_on_exception = true
threads = []
5.times do |value|
threads << Thread.new(value) do |i|
raise "An error occurred!" if i == 3
print "#{i}\n"
end
end
# Use Thread.join to wait for threads to finish
threads.each { |t| t.join }
Output:
0
1
2
main.rb:12:in `block (2 levels) in ': An error occurred! (RuntimeError)