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:

    1. class MyException < Exception
    2. end
    3. class MyOtherException < Exception
    4. 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:

    1. begin
    2. raise "OH NO!"
    3. rescue
    4. puts "Rescued!"
    5. end
    6. # Output: Rescued!

    To access the rescued exception you can specify a variable in the rescue clause:

    1. begin
    2. raise "OH NO!"
    3. rescue ex
    4. puts ex.message
    5. end
    6. # Output: OH NO!
    1. begin
    2. raise MyException.new("OH NO!")
    3. rescue MyException
    4. puts "Rescued MyException"
    5. end
    6. # Output: Rescued MyException

    And to access it, use a syntax similar to type restrictions:

    Multiple rescue clauses can be specified:

    1. begin
    2. # ...
    3. rescue ex1 : MyException
    4. # only MyException...
    5. rescue ex2 : MyOtherException
    6. rescue
    7. # any other kind of exception
    8. end

    You can also rescue multiple exception types at once by specifying a union type:

    1. begin
    2. # ...
    3. rescue ex : MyException | MyOtherException
    4. # only MyException or MyOtherException
    5. rescue
    6. # any other kind of exception

    else

    An else clause is executed only if no exceptions were rescued:

    1. begin
    2. something_dangerous
    3. rescue
    4. # execute this if an exception is raised
    5. else
    6. # execute this if an exception isn't raised
    7. 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:

    1. begin
    2. something_dangerous
    3. ensure
    4. puts "Cleanup..."
    5. end
    6. # Will print "Cleanup..." after invoking something_dangerous,
    7. # 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:

    1. def some_method
    2. something_dangerous
    3. rescue
    4. # execute if an exception is raised
    5. end
    6. # The above is the same as:
    7. def some_method
    8. begin
    9. something_dangerous
    10. rescue
    11. # execute if an exception is raised
    12. end
    13. end

    With ensure:

    1. something_dangerous
    2. ensure
    3. # always execute this
    4. end
    5. # The above is the same as:
    6. def some_method
    7. begin
    8. something_dangerous
    9. ensure
    10. # always execute this
    11. end
    12. end
    13. # Similarly, the shorthand also works with blocks:
    14. (1..10).each do |n|
    15. # potentially dangerous operation
    16. rescue
    17. # ..
    18. else
    19. # ..
    20. ensure
    21. # ..
    22. 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:

    1. begin
    2. a = something_dangerous_that_returns_Int32
    3. ensure
    4. puts a + 1 # error, undefined method '+' for Nil
    5. 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:

    1. begin
    2. a = 1
    3. something_dangerous
    4. ensure
    5. puts a + 1 # error, undefined method '+' for Nil
    6. 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:

    1. array = [1, 2, 3]
    2. array[4] # raises because of IndexError