Ruby

Exception Handling in Ruby

Exception handling refers to the process of predicting and defining ways to handle errors raised in a program during execution. An error, in most cases, refers to an unexpected event or occurrence during the execution of the program. For example, an error may occur while reading a file either due to the file not existing or the user not having the right permission to read or write to the file.

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:

begin
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:

def err_me

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.

  1. SyntaxError
  2. IOError
  3. RegexpError
  4. ThreadError
  5. ZeroDivisionError
  6. NoMethodError
  7. IndexError
  8. NameError
  9. 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:

begin
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:

$ ruby err-handling.rb
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:

begin
    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:

begin
    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.

ZeroDivisionError caused me to die ⚰️

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:

begin
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 πŸ˜€

& I will always run πŸš€

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:

catch(:kill_me_now) do
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:

After throw, nothing will run!'
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.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list