In every system, no matter how well designed, there will come a time when things don't go as planned. Sometimes, an argument is passed that isn't what we thought it would be. Sometimes someone tries to divide by 0. Sometimes you just type something wrong. When these situations arise, programs usually fail and exit. Ruby is no different, but along the way, it tries to manage the failure by creating an object called an Exception.
Exceptions have different classes that attempt to describe the circumstances that gave rise to the system's failure. The really useful thing about exceptions, though, is that they allow us to anticipate failure, and, in some cases, recover from them to save our program. This is even used as a way of bubbling messages up through the call stack.
Exceptionclass handles nearly every kind of error that can happen while your program is running, including syntax errors.
To see an example, let's create a new file called error.rb:
Here's an example, caused by typing some random letters and trying to run them as Ruby code:
$ ruby error.rb error.rb:1:in `<main>': undefined local variable or method `asldkfj' for main:Object (NameError)
Like so much else in the language, errors are objects in Ruby. In this case, we have a
NameError, as the output helpfully tells you in the parentheses at the end there. If we look at the
NameErrorclass' ancestry, we see that it inherits from
irb(main):0> NameError.ancestors=> [DidYouMean::Correctable, NameError, StandardError, Exception, Object, Kernel, BasicObject]
You might think "That's great that errors are objects, but what's the point of that? Your program crashes as soon as they're created, so how could you ever use them?"
In fact you couldn't make use of error objects for that very reason, except for a special method in Ruby called
rescuestops your program from crashing temporarily after an exception is created, and gives you a chance to recover, should you so choose. To use
rescue, you need to set it up with a special context block using the words
end. Here's how that looks:
beginasldkjrescueputs "um, that makes no sense"end
Now running that file produces:
Rescuing exceptions works almost like an if/else statement. You can even use
elseif you want to
begin"no errors here"rescueputs "um, that makes no sense"elseputs "everything's fine, no errors"end
rescuelike this will catch any kind of error that occurs while the begin...end block is running, even one's that we weren't expecting.
begin1/0rescueputs "um, that makes no sense"end
Actually this isn't good, because
1/0"makes sense", in a way, it's just something that isn't allowed. To narrow down the conditions that we want to rescue, pass the name of the exception class you want to rescue as an argument:
beginasldkfrescue NameErrorputs "That thing you wrote doesn't exist, sorry."end
But now if a different type of error happens inside the begin...end block...
begin1/0rescue NameErrorputs "That thing you wrote doesn't exist, sorry."end
$ ruby error.rb error.rb:2:in `/': divided by 0 (ZeroDivisionError) from error.rb:2:in `<main>'
rescuewon't address any errors that happen outside your begin...end block:
begin"nothing special here"rescueputs "um, that makes no sense"endaskjdf
$ ruby error.rb error.rb:7:in `<main>': undefined local variable or method `askjdf' for main:Object (NameError)
So far we've talked about controlling for which class of exception we want to
rescue. This allows us to respond in specific ways to different types of errors. But sometimes we want more information than just the type of error. We might want to know the exact error message, or the line on which it happened.
To enable this, rescue gives us the option to store the actual exception instance in a variable, so that we can call methods on it later to get more information.
beginasldkfjrescue NameError => my_error_objectputs my_error_object.messageputs my_error_object.backtraceend
$ ruby error.rb undefined local variable or method `asldkfj' for main:Object error.rb:2:in `<main>'
The above code printed out the error's message as well as its stack trace.
The counterpart to rescue is raise. It allows you to create an error out of thin air!
In the examples above, see what happens if you replace the lines with a random error-generating string on them:
with this statement:
See if you can figure out how to do the same thing with this error:
Finally, see what happens when you do the following:
with no argument afterwards.
Try it in a file all by itself, then try it inside one of your rescue blocks.