class MyError < StandardError
end
To see how exceptions are handled, let us look at this example:
try_counter = 0
begin
try_counter += 1
puts 'Here 1'
raise MyError.new "Text" unless try_counter > 5
puts 'Here 2'
rescue MyError
puts 'Here 3 - MyError encountered'
retry
rescue StandardError
puts "Here 4 - Other error encountered (#{$!.inspect})" + caller.inspect
raise
else
puts 'Here 5 - No errors'
ensure
puts 'Here 6 - Always done'
end
Exceptions are handled by blocks. The risky code goes into the first chunk of the block. If an exception is encountered, the program will jump to the end of that chunk and look for an appropriate action. In the above example, it will first look at
rescue MyError
. If the exception is of the MyError
class - or a sub-class - then the program will not run the code folling the rescue. Note that you can list multiple exception classes, separated by commas.Otherwise, it will look further. The next rescue is for
StandardError
, so this will catch all errors of the StandardError
type or its sub-classes - except for MyClass
, as that would have been caught earlier. All rescuable exceptions must inherit from StandardError
(but see later), so this is set up to catch all exceptions, but that need not be the case; uncaught exceptions will be passed on up the stack to whatever else might handle them.The
else
chunk will get performed after the initial chunk has completed successfully (i.e., without raising an exception). Finally, the ensure
chunk gets performed whatever the outcome.An exception is raised using the
raise
keyword. Exceptions are just objects, so are instantiated just like any other object. They typically take a string parameter, which you can use to give a descriptive message. Alternatively, you use this form:raise MyError.new "Test"
raise MyError, "Text", caller
raise MyError, "Text"
raise "Text" # For the default RuntimeError
You can put a
retry
in a rescue chunk, as in the example above. The program will jump back to the start of the block, and start again. In the second rescue
chunk, there is a raise
on its own. This will pass the exception ouside of the block, to be handled by some higher up error handling system.Also in that chunk, note that the
$!
code is the exception. If you prefer, you can set your own name for this.rescue StandardError => errHere is the output from the first example:
puts "Here 4 - Other error encountered (#{err.inspect})" + caller.inspect
raise
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 2
Here 5 - No errors
Here 6 - Always done
Stack Trace
To see the stack trace, use the
backtrace
method. This returns an array of strings. To just see the top dozen entries, you could use this:$!.backtrace[0..12] * "\n"
Exceptions that do not inherit from StandardError
The superclass for all exceptions is
Exception
. The theory is that all exceptions that a program should be expected to recover from are either StandardError
or a subclass of it, which is why I described them as "rescuable exceptions " earlier. If no exception is specified, rescue will default to StandardError
.However, there may be times you want to recover from other exceptions. A particular example I came across is a
SyntaxError
thrown by ERB. The offending syntax was in a data file, not my application; my application should have caught the error, and reported it back to the user. This is entirely different to a syntax error in my code, and in my opinion ERB should throw its own exceptions that inherit from StandardError
. Time-out errors are another example, as discussed here.To catch all exceptions use:
rescue Exception
The rescue statement modifier
Rescue can be used in a way analogous to if and unless. For example:
test rescue do_stuff
This statement will run the method test, and if it encounters an error (specifically StandardError or subclasses) it will invoke do_stuff. You can use this to assign a default value if a method fails, like this:
s = test rescue "Default value"
The rescue stament returns the value "Default Value". You could write it with brackets, which might help make sense of it:
s = (test rescue "Default value")
You can concatenate statement with semi-colons, but cannot use blocks.
s = test rescue do_stuff; "Default value"
See also:
http://whynotwiki.com/Ruby_/_Exception_handling
Catch and Throw
The catch and throw facility in Ruby is not really exception handling at all, though it does have similarities. As it shares keywords with Java exception handling, it seems to get lumped into any discussion on exceptions. This page is no different.
Calling a
throw
will interrupt the program flow, causing it to jump to the named catch
. Use this format to set up a catch block. Any time throw :my_label
is called within the block (including within methods called from the block).catch :my_label do
do_stuff
throw :my_label
do_not_do this_stuff
end
You can use the throw to return a value to catch (which will be nil otherwise).
value = catch :my_label do
do_stuff
throw :my_label, "My result"
do_not_do this_stuff
end
Struggling with Ruby: Contents Page
1 comment:
Finally a clear explanation. The pickaxe book is amazingly obtuse about this process. Thanks!
Post a Comment