Exception handling
You raise exceptions by invoking a top-level method. Unlike other keywords, raise
is a regular method with two overloads: one accepting a String and another :
The String version just creates a new Exception instance with that message.
Only Exception
instances or subclasses can be raised.
Defining custom exceptions
To define a custom exception type, just subclass from Exception:
class MyException < Exception
end
class MyOtherException < Exception
end
You can, as always, define a constructor for your exception or just use the default one.
To rescue any exception use a begin ... rescue ... end
expression:
begin
raise "OH NO!"
rescue
puts "Rescued!"
end
# Output: Rescued!
To access the rescued exception you can specify a variable in the rescue
clause:
begin
raise "OH NO!"
rescue ex
puts ex.message
end
# Output: OH NO!
begin
raise MyException.new("OH NO!")
rescue MyException
puts "Rescued MyException"
end
# Output: Rescued MyException
And to access it, use a syntax similar to type restrictions:
Multiple rescue
clauses can be specified:
begin
# ...
rescue ex1 : MyException
# only MyException...
rescue ex2 : MyOtherException
rescue
# any other kind of exception
end
You can also rescue multiple exception types at once by specifying a union type:
begin
# ...
rescue ex : MyException | MyOtherException
# only MyException or MyOtherException
rescue
# any other kind of exception
else
An else
clause is executed only if no exceptions were rescued:
begin
something_dangerous
rescue
# execute this if an exception is raised
else
# execute this if an exception isn't raised
end
An else
clause can only be specified if at least one rescue
clause is specified.
An ensure
clause is executed at the end of a begin ... end
or begin ... rescue ... end
expression regardless of whether an exception was raised or not:
begin
something_dangerous
ensure
puts "Cleanup..."
end
# Will print "Cleanup..." after invoking something_dangerous,
# regardless of whether it raised or not
Or:
Short syntax form
Exception handling has a short syntax form: assume a method or block definition is an implicit begin ... end
expression, then specify rescue
, else
, and ensure
clauses:
def some_method
something_dangerous
rescue
# execute if an exception is raised
end
# The above is the same as:
def some_method
begin
something_dangerous
rescue
# execute if an exception is raised
end
end
With ensure
:
something_dangerous
ensure
# always execute this
end
# The above is the same as:
def some_method
begin
something_dangerous
ensure
# always execute this
end
end
# Similarly, the shorthand also works with blocks:
(1..10).each do |n|
# potentially dangerous operation
rescue
# ..
else
# ..
ensure
# ..
end
Variables declared inside the begin
part of an exception handler also get the Nil
type when considered inside a rescue
or ensure
body. For example:
begin
a = something_dangerous_that_returns_Int32
ensure
puts a + 1 # error, undefined method '+' for Nil
end
The above happens even if something_dangerous_that_returns_Int32
never raises, or if a
was assigned a value and then a method that potentially raises is executed:
begin
a = 1
something_dangerous
ensure
puts a + 1 # error, undefined method '+' for Nil
end
Although it is obvious that a
will always be assigned a value, the compiler will still think a
might never had a chance to be initialized. Even though this logic might improve in the future, right now it forces you to keep your exception handlers to their necessary minimum, making the code’s intention more clear:
Alternative ways to do error handling
Although exceptions are available as one of the mechanisms for handling errors, they are not your only choice. Raising an exception involves allocating memory, and executing an exception handler is generally slow.
The standard library usually provides a couple of methods to accomplish something: one raises, one returns nil
. For example:
array = [1, 2, 3]
array[4] # raises because of IndexError