This tutorial will show you how to implement exception handling in Ruby using the raise and rescue blocks.
Basic Usage
Most programming languages implement exception handling using the try and catch block. However, like everything else in Ruby, the keywords are more descriptive.
We can express the general syntax as shown below:
raiseexception
# raise ecxeption
rescue exception
# rescue block
end
We enclose the exception handling block in a begin and end statement. Inside these statements, we define the raise and rescue blocks.
In the raise, we define the exception, which we can raise manually or have the Ruby interpreter generate it. By default, the parameter for the raise block is RuntimeError
Next is the rescue block. As the name suggests, this block comes to the rescue when an exception occurs. It takes control of the program’s execution.
Ruby will compare the exception raised from the raise block against the parameters passed to the rescue block. If the exception is of the same type or a superclass, it triggers the rescue block.
Example Of Exception Handling in Ruby
We can implement a simple example to illustrate how exception handling works in Ruby:
begin
puts "Hi there!"
raise "string type"
rescue
puts "Never mind, I am fixed!"
end
end
err_me
In the above example, we define a function with an exception block.
We manually raise an exception, which interrupts the program’s execution flow and enters the rescue block. This performs the actions in the block—in this case, a put statement and exits.
If you add any code block immediately after the raise and before the rescue block, they do not execute because the rescue block immediately handles the program flow.
By default, the rescue block uses the StandardError parameter. However, there are other types of errors in Ruby, including.
- SyntaxError
- IOError
- RegexpError
- ThreadError
- ZeroDivisionError
- NoMethodError
- IndexError
- NameError
- TypeError
And more.
To raise and handle a specific error type, we can pass it to the raise block as a parameter. Here is an example:
raiseZeroDivisionError
rescue =>exception
puts exception.message
puts exception.backtrace.inspect
end
In the above example, we raise a ZeroDivisionError. We then jump into the rescue block, which prints the specific exception type and traces the source.
The resulting output is:
ZeroDivisionError
["err-handling.rb:2:in `<main>'"]
Other Exception Blocks
Besides the main raise and rescue block, Ruby also provides us with other blocks we can implement to handle errors.
They include:
Retry Block
The retry block is used to re-run the rescue block after raising the exception. Here is an example:
raise ZeroDivisionError
puts "I don't run 😢"
rescue => exception
puts "#{exception.message} caused me to die ⚰️"
retry
end
If we run the code above, it will print the message inside the rescue block. It will encounter the retry block, which jumps into the rescue block.
A common use case of retry blocks is probing errors using brute force. An example would be to keep reloading a page when the connection is down until the error resolves.
CAUTION: Be careful when using the retry block because it is a common source of infinite loops.
Ensure Block
If you have programmed in another language such as Python, you are probably familiar with the finally block. The ensure block in Ruby performs similarly to the finally block in other programming languages.
The ensure block always runs at the end of the code. Irrespective of whether the raised exception was handled correctly or the program execution terminates, it always runs or executes.
Here is an example:
raise ZeroDivisionError
puts "I don't run 😢"
rescue => exception
puts "#{exception.message} caused me to die ⚰️"
ensure
puts "I will always run 🚀"
end
In this case, the code above will print an exception message and finally run the ensure block.
I will always run 🚀
Else Block
If no exception is raised, we can implement a block to do an action using the else statement.
For example:
rescue => exception
puts "#{exception.message} caused me to die ⚰️"
else
puts "Trust me, I ran successfully 😀"
ensure
puts "& I will always run 🚀"
end
The else block is placed between the rescue and ensure block. In the example above, you will notice it is missing a raise block, which causes the else block to run.
Here is an example output:
Trust me, I ran successfully 😀
Lightweight Exception Handling
The raise and rescue blocks are a handy way to perform an action when an error occurs. However, because error handling builds a stack trace to help with debugging, it can easily become problematic within your program. This is where the catch and throw blocks come in.
To implement a catch-throw block, you start by defining the label using the catch keyword. Once ruby encounters a throw block that references the catch block, it stops the execution and jumps to the catch block.
Let us use an example to illustrate this concept. Consider the messy nesting shown in the code below:
langs = ["Python", "Ruby", "C++", "C#"]
foriinlangsdo
for index in 1..5
if index == 3
ifi == "C#"
puts "After throw, nothing will run!'"
throw(:kill_me_now)
puts "I am C#"
end
end
end
end
end
puts "Oh boy! That was a long one!"
We start by using the catch keyword and pass the label inside a pair of parentheses. Once we run the code, it will execute all the nested loops and if statements until it encounters the throw statement referencing the catch.
That will immediately terminate executing and exit back to the level of the catch statement.
Here is an example output:
Oh boy! That was a long one!
Conclusion
This tutorial has shown you how to implement error handling in Ruby using the raise and rescue blocks.