I'm trying to understand exceptions in Ruby but I'm a little confused. The tutorial I'm using says that if an exception occurs that does not match any of the exceptions identified by the rescue statements, you can use an "else" to catch it:
begin # - rescue OneTypeOfException # - rescue AnotherTypeOfException # - else # Other exceptions ensure # Always will be executed end
However, I also saw later in the tutorial "rescue" being used without an exception specified:
begin file = open("/unexistant_file") if file puts "File opened successfully" end rescue file = STDIN end print file, "==", STDIN, "\n"
If you can do this, then do I ever need to use else? Or can I just use a generic rescue at the end like this?
begin # - rescue OneTypeOfException # - rescue AnotherTypeOfException # - rescue # Other exceptions ensure # Always will be executed end
Ruby also provides a separate class for an exception that is known as an Exception class which contains different types of methods. The code in which an exception is raised, is enclosed between the begin/end block, so you can use a rescue clause to handle this type of exception.
When you raise an exception in Ruby, the world stops and your program starts to shut down. If nothing stops the process, your program will eventually exit with an error message.
The method definition itself does the work of begin , so you can omit it. You can also do this with blocks. Now, there is one more way to use the rescue keyword without begin .
In Ruby we have a way to deal with these cases, we have begin, end(default try catch) and we can use try and catch, both try catch and raise rescue used for the same purpose, one will throw exception(throw or raise) with any specific name inside another(catch or rescue).
The else
is for when the block completes without an exception thrown. The ensure
is run whether the block completes successfully or not. Example:
begin puts "Hello, world!" rescue puts "rescue" else puts "else" ensure puts "ensure" end
This will print Hello, world!
, then else
, then ensure
.
Here's a concrete use-case for else
in a begin
expression. Suppose you're writing automated tests, and you want to write a method that returns the error raised by a block. But you also want the test to fail if the block doesn't raise an error. You can do this:
def get_error_from(&block) begin block.call rescue => err err # we want to return this else raise "No error was raised" end end
Note that you can't move the raise
inside the begin
block, because it'll get rescue
d. Of course, there are other ways without using else
, like checking whether err
is nil
after the end
, but that's not as succinct.
Personally, I rarely use else
in this way because I think it's rarely needed, but it does come in handy in those rare cases.
EDIT
Another use case occurred to me. Here's a typical begin
/rescue
:
begin do_something_that_may_raise_argument_error do_something_else_when_the_previous_line_doesnt_raise rescue ArgumentError => e handle_the_error end
Why is this less than ideal? Because the intent is to rescue
when do_something_that_may_raise_argument_error
raises ArgumentError
, not when do_something_else_when_the_previous_line_doesnt_raise
raises.
It's usually better to use begin
/rescue
to wrap the minimum code you want to protect from a raise
, because otherwise:
raise
rescue
is harder to decipher. Someone (including your future self) may read the code and wonder "Which expression did I want to protect? It looks like expression ABC... but maybe expression DEF too???? What was the author intending?!" Refactoring becomes much more difficult.You avoid those problems with this simple change:
begin do_something_that_may_raise_argument_error rescue ArgumentError => e handle_the_error else do_something_else_when_the_previous_line_doesnt_raise end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With