In this section, we will learn what exception and exception handling are and how to handle exceptions in Ruby.
What is exception in Ruby?
Any time your program fails to run its task properly because of a wrong input or malfunction code in the program that causes the program to crash, we say an exception occurred.
Basically, exception is a type of error in your program that causes it to stop working the way it used to.
For example, let’s say part of your program is in charge of taking two input values from a user, divide those values by each other and then return the result back.
Now imagine if a user enters two values like 5 and 0 and your program should divide the value 5 by 0! Here if your code is not written properly, this will be a source of exception which will crash your program eventually!
Example: throwing exception in Ruby
def divide (num1 , num2) result = num1 / num2 puts result end divide(10, 2) divide(5, 0)
5 divided by 0 (ZeroDivisionError)
As you can see, the first time we called the `divide` method, the operation worked properly, and we got the value 5 as a result. But then when we passed values 5 and 0 as the argument of this method, things went out of control and the `ZeroDivisionError` error occurred, which stopped the program from continuing to run its instructions.
What is Exception or Error Handling in Ruby?
Now in Ruby there are ways which can control and stop a program from crashing as a result of potential errors!
In short, the methods we used to handle errors in Ruby is called Exception or Error Handling.
begin rescue Blocks in Ruby
In Ruby, the standard method of handling errors is by using the `begin` and `rescue` blocks.
Basically, the `begin` clause is a block where we put the part of a code that might raise an exception and the `rescue` clause is another block defined within the body of a begin block and it’s capable of rescuing (AKA handling) an exception if it happened in the related begin block.
In short, when an exception arises in the body of a begin block, your program will stop running the rest of instructions in the body of the begin block and start looking for the rescue clause that is related to that begin block.
If it found one that can handle the exception, it will jump into its body and start to run the instructions there! For example, let’s say the job of the source code we’ve written in the body of the begin clause is to take two values and divide them and then return the result. Now the rescue clause here is in charge of handling the error that happens as a result of dividing an integer by 0! So if such error happens in the begin clause, your program automatically jumps into the body of rescue clause and runs any instructions there (Which could be sending a message to the user and asking for a non-zero value etc.)
Alright, now let’s get into the syntax of begin and rescue blocks and then we will continue our discussion on how to practically use them.
How to Declare begin rescue Blocks? (begin rescue blocks syntax)
begin #instructions to run in the body of begin clause rescue ExceptionName => instance #instructions to run if an Error of type ExceptionName occurred within the body of the end
`begin end`: first of all, in order to create a block for the begin clause, we start with the keyword begin and close its boundary using the `end` keyword. Now the body we got is the place where we can put instructions that might potentially throw an error.
`rescue`: this is the part where we define the handler for a potential exception that might araise in the begin clause.
`ExceptionName => instance`: Exceptions that are raised in a program are objects as well! Each exception is of a specific type! We can even create our own exception if we want (you’ll learn how to create your own exception class in later sections). For now, just remember that a rescue class is designed to handle a specific type of exception! For example, one of them that we’ve seen so far is an exception of type ` ZeroDivisionError` class. So if we want to make the `rescue` clause to handle this specific exception, we can set the `ExceptionName` to `ZeroDivisionError` class and so the `rescue` clause will be only invoked if the raised exception is of this type.
Also, the `instance` part after `=>` symbol is a reference to the raised exception!
Each exception has multiple methods that can be called and get information to learn about that raised exception! For example, one of these methods is called `message` which we can call and see the reason behind the raised exception.
Note: we use `=>` symbol to separate the type of exception from a reference (the `instance`) to that raised exception.
Also remember that within the body of a begin block, we can set more than one `rescue` clause! For example, if the source code in the begin clause might throw multiple types of exceptions, then we can put an equal number of rescue clause for those potential exceptions in the begin block.
begin #instructions in the begin block rescue ExceptionTypeOne => instance #instructions rescue ExceptionTypeTwo => instance #instructions rescue ExceptionTypeN => instance #instructions end
Also, other than setting the type for the rescue clause, we can also ignore the type altogether! This way, that rescue clause will be invoked for any type of exception that might be raised in the body of the begin block.
We use a rescue clause that doesn’t have any type for a general purpose. Basically, let’s say you know that the source code in a begin clause might raise two specific exceptions! So you put the handler for those type of exception using two different rescue clause. But then you’re not sure if the source code can throw another exception as well! In that case we can simply put a general handler (using the rescue clause without setting a type for it) so then that clause will be invoked any time an exception raised that no other handler in the begin block could handle the raised exception.
Here’s the syntax:
begin #instructions in the begin block rescue ExceptionTypeOne => instance #instructions rescue ExceptionTypeTwo => instance #instructions rescue => instance #instructions end
Note that for the last `rescue` clause, we didn’t set any type! This means the last handler is a general one and will handle any type of exception that the other two handlers in the example above couldn’t handle!
Also, because a general exception handler is capable of handling any type of exception, it must appear as the last handler in a begin block! Basically, after any rescue clause with a specific type. So that those specific ones get a chance of being checked as well when an exception raised.
Example: exception handling via begin rescue blocks in Ruby
def divide (num1 , num2) result = num1 / num2 puts result end begin divide(10, 2) divide(5, 0) puts “Closing the begin block” rescue ZeroDivisionError => e puts e.message rescue => e puts e.message end puts "The message after the begin block"
5 divided by 0 The message after the begin block
Exception handling and control flow in Ruby
In the example above, we have one begin block with two handlers.
The first handler is designed to handle any exception that are of type `ZeroDivisionError` that might happen in the body of the begin clause and the second handler is a general one that will be called for another type of exceptions if they raised in the begin block.
Now, in the body of the begin block there are two calls to the same method. The first call runs perfectly fine and no error returns as a result!
But the second one is a source of error!
Basically, for the second call, first the program invokes the body of the method and starts to run its body using the values of 5 and 0.
So when in the body of the `divide` method, the program starts to run this statement:
result = num1 / num2
It realizes that a number can’t be divided by the value 0 and so this is where the actual error occurs.
First of all, whenever your program faces an error, the normal flow of running instructions will stop and your program will immediately start to look for a handler to handle the raised error!
Basically at this stage, our program will first check the body of the divide method to see if the statement that just ran was enclosed by a begin- rescue clause or not! Well, in this particular example, the answer is no! Basically, there’s no handler in the body of the `divide` method that could be used to handle the error.
So then the program will terminate the method immediately (without going into anymore instructions in this method) and get back to the caller of the method to see if there’s a handler there that could handle the raised error.
So because the call to the divide method occurred in the body of a begin clause, our program will start to check the available rescue clauses to see which one is suitable to handle the raised exception.
Here the answer is the first rescue clause! Because it has the specific type as the raised exception hand so it is the choice of our program. Hence, its body will be executed.
Now, after handling the error in the rescue clause, the program won’t return to the body of the `divide` method but instead will continue to run the instructions after the `begin` block (after the `end` clause). For this example, there’s only one statement after the `begin` block and that’s a call to the `puts` method. So our program will run this statement and after that, will terminate the program.
Ruby Exception Handling Notes:
- When a handler is called, if there are other handlers after the one that just ran, they will be ignored because from the perspective of a program, the error is handled after running a handler and so there’s no reason to run another handler!
- By default, your program after handling an error will never return to the body of the related begin block to continue to run the instructions there! It will jump out of the begin block and continue to run the instructions after the block! That’s why in the example above, we see that the `puts “The message after the begin block”` statement ran instead of the last statement in the begin block.
- If there’s no handler in a program to handler a raised exception, we finally get an error and the program will stop (crashes).