-
Notifications
You must be signed in to change notification settings - Fork 1
Exceptions and Throw
This page describes the Exception class and some of its subclasses, how exception objects are manipulated in Ruby, and the throw/catch feature (which does not employ Exception objects at all).
For details on how exceptions can be caught, see Ruby Expressions.
Exception is the root class of all exceptions in Ruby - only instances of Exception and its subclasses can be raised as exceptions. Exception objects store the following state:
- A message, which describes the exception (e.g. "invalid value for x: y"), accessed by the
message,to_sand==methods and modified by theinitializeconstructor method - A backtrace, accessed by the
backtracemethod and modified by theset_backtracemethod
This Exception object state (and, in general, additional state stored by Exception subclasses defined by core/extension libraries) is stored in the object as special hidden instance variables - they can't be directly accessed in Ruby code, however they do get copied in cloning/duping operations.
Ruby generally does not access the state directly, rather it uses the accessor/mutator methods - you are not required to store the state using the default native facilities, and may override these methods to access and store state how and if you wish as long as you observe the method contracts. However, note how by default Exception#exception directly modifies internal state as described below.
The message method is expected to return a String instance, or an object that can be converted to one via to_str. It is used by Ruby (along with class name, the backtrace method, and perhaps other VM information) when printing out details of an unhandled exception that caused program termination.
The backtrace method is expected to return one of the following:
- An array of Strings, or String-convertible objects via
to_str, that each describe a level of the Ruby stack in the backtrace from the innermost level outwards - A single String or String-convertible object, which Ruby will treat as being wrapped in an Array.
-
nil, which means no backtrace is associated with this object
The set_backtrace method is expected to store a backtrace object which can be subsequently accessed by the backtrace method. When the Ruby VM invokes set_backtrace it passes a valid backtrace object (i.e. an array of Strings).
Ruby code can build its own backtrace via Thread#backtrace (note totally unrelated to Exception#backtrace).
When Ruby enters a rescue or ensure clause, the exception that caused the clause to be entered is stored in a hidden dynamic variable - the exception context. No exception context is defined for ensure clauses if the clause was entered through normal execution, due to a control transfer statement, or due to a thread kill instruction.
Ruby code can access the exception context (via core-defined global variables and raise methods), however it is scoped differently to regular dynamic variables and other types of Ruby scoping. An exception context is scoped to cover code executing within the instance of the rescue/ensure clause associated with the exception, and recursively any methods/blocks that are called from within that clause, until another rescue/ensure clause is nested within which defines its own exception context. This is a purely runtime, stack-based scoping - the lexical location of a particular line of code or the closures it has access to have no bearing on what (if any) exception context is in scope for that line.
Ruby implements an unusual pattern for raising exceptions. Methods for raising exceptions, such as Kernel#raise, don't directly accept an Exception object to be thrown - they accept a template object from which an exception will be provided, by calling the template's exception method. This feature is basically here to permit some syntactic conveniences as shown below.
Ruby methods for raising exceptions (such as Kernel#raise, Kernel#fail, Thread#raise) are composed of 2 phases:
- Create the exception
- Raise the exception
In the case of Thread#raise, the exception is created, but isn't raised directly - it will be raised by the targeted thread.
All of these methods may be invoked with arguments as follows:
raise
raise <string>
raise <template>, [ <argument> ], [ <backtrace> ]
The exception creation phase works as follows:
- With no arguments:
- If an exception context exists, an exception will be provided by invoking
.exception()on the exception context exception, with no arguments - Otherwise, an exception will be created via
RuntimeError.new- note that this creation is actually deferred and will be performed at the beginning of the exception raising stage - With a
<string>, which is either an instance of String or an object that can be converted to a String viato_str, an exception will be created viaRuntimeError.new(<string>) - With a
<template>, an exception will be provided by invoking<template>.exception()or, if<argument>is provided,<template>.exception(<argument>). If<backtrace>is provided, it will subsequently be stored on the exception via.set_backtrace(<backtrace>)
The exception raising phase works as follows:
- Invokes
.backtrace()on the exception to determine if it doesn't have a backtrace (i.e. returnsnil) - If so, creates a backtrace and invokes
.set_backtrace(<backtrace>)on the exception - Raises the exception
Any object can be an exception template (with one exception - nil), all it has to do is implement the exception method. exception is expected to return an instance of Exception (which may be the template object itself), and it's expected to take 1 optional argument, of any type, which is passed in directly from a Kernel#raise (or similar method) argument and is intended to serve as a template-interpreted parameter in the exception object creation process.
There are 2 default implementations of exception:
-
Exception::exception, which implements the default behaviour ofClass#new -
Exception.exception, which expects an optional single argument ofselfor a message, and either: - Returns itself, if called with no arguments, or with 1 argument which is itself - note that it is probably sensible to preserve this behaviour if you override
exception - Creates a clone of itself (with the default implementation of
Object#clone), and invokes the default implementation ofException#initializeon the new object withexception's arguments- Note that
initializeaccepts 1 optional argument and will reset the internal backtrace state tonil, and the internal message state to the argument (or nil if not provided)
- Note that
The default implementations of exception suggest, for example, the following constructs:
raise exc # Where exc is an Exception object, raises it directly
raise exc, exc, bt # Where bt is a backtrace, sets the backtrace on the exception and raises it
raise exc, "hello" # Raises a clone of exc with the message "hello" and a new backtrace
raise StandardError, "hello" # Raises a new instance of StandardError created with the argument "hello"
Note that an exception class's initialize method should be prepared to be invoked with 1 optional argument through the Exception::exception method, unless that class overrides its Exception::exception method for different behaviour.
The following table depicts the exception hierarchy provided by the Ruby core.
| Class | Description |
|---|---|
Exception |
The root exception class - all objects to be raised as exceptions must inherit from this class. |
>NoMemoryError |
Raised when memory allocation failed (out of memory) |
>SystemStackError |
Raised when ran out of C stack or Ruby VM stack, e.g. method or proc calls nested too deep |
>SecurityError |
Raised when Ruby security features are enabled, and a disallowed insecure operation was attempted |
>SystemExit |
Raised to instruct the Ruby VM to terminate, typically by the Kernel#exit or Kernel#abort methods |
>SignalException |
Raised when the Ruby VM receives an OS signal (other than the SIGINT signal) that it has been configured to raise as an exception |
>>Interrupt |
Like SignalException, for the SIGINT signal |
>ScriptError |
A container for exceptions due to problems with Ruby code |
>LoadError |
Raised when the Ruby VM is unable to open a Ruby sourcefile or extension library that it is attempting to load |
>SyntaxError |
Raised when the Ruby VM encounters invalid code when trying to compile Ruby code, e.g. a Ruby sourcefile or eval string. |
>>NotImplementedError |
Raised when functionality is invoked which is not available or not yet supported on the current platform |
>StandardError |
A container for "regular" exceptions, i.e. all exceptions other than the special circumstance exceptions described above. The rescue clause catches StandardError by default. Application-defined exceptions should inherit from this class. |
>>ArgumentError |
Raised when invalid arguments were provided to a method (and there is not a more specific exception type that could be thrown instead) |
>>EncodingError |
A container for exceptions related to problems with translating between different String character set encodings. Note that a failure due to invalid byte sequences when interpreting against a single encoding is raised as an ArgumentError. |
>>>Encoding::CompatibilityError |
Raised when an operation requires strings to be bytecode-compatible with different character sets, and they are not |
>>>Encoding::ConverterNotFoundError |
Raised when Ruby can't transcode between character encodings because the necessary converters are not available |
>>>Encoding::InvalidByteSequenceError |
Raised when Ruby can't transcode between character encodings due to invalid byte sequences in the source string |
>>>Encoding::UndefinedConversionError |
Raised when Ruby can't transcode between character encodings because the target encoding does not support a character in the source string |
>>FiberError |
Raised for errors associated with creating and manipulating Fibers |
>>IOError |
Raised for general errors associated with I/O operations, other than OS-raised errors which are raised as SystemCallErrors |
>>>EOFError |
Raised for end-of-file errors associated with I/O operations |
>>IndexError |
Raised when an invalid index was specified, e.g. into an array |
>>>KeyError |
Raised when an invalid key was specified, e.g. into a hash table |
>>>StopIteration |
Raised when using external (pull-style) iteration with an Enumerator object when the iteration is complete. Rescued by Kernel#loop so this method can be used as a convenience to convert from pull-style to push-style enumeration. |
>>LocalJumpError |
Raised when a yield or control transfer instruction fails to resolve |
>>Math::DomainError |
Raised when a math function is invoked with arguments outside its valid domain |
>>NameError |
Raised for errors associated with identifiers specified in Ruby code` |
>>>NoMethodError |
Raised when attempting to call a method on an object and the call is not resolved, either directly or via method_missing. method_missing is required to raise this exception when invoked with methods that it doesn't implement. |
>>RangeError |
Raised for general errors where a numerical argument is outside the valid range for the operation |
>>>FloatDomainError |
Raised when attempting to convert special floating-point values (such as Infininty and NaN) to other numeric types which don't support them |
>>RegexpError |
Raised for invalid regular expressions |
>>RuntimeError |
A generic exception class for errors not falling in other categories. This is the default exception class raised by e.g. Kernel#raise if no other exception class is specified. |
>>SystemCallError |
A container for exception classes that represent errors returned by OS system calls |
>>>Errno::... |
SystemCallError has a subclass for each type of OS-returned error, in the Errno module's namespace, e.g. Errno::EINVAL
|
>>ThreadError |
Raised for errors associated with creating and manipulating Threads and Mutexes |
>>TypeError |
Raised for general errors where an argument is not of the expected class |
>>ZeroDivisionError |
Raised when attempting to divide by 0 |
In the course of execution, Ruby will throw an exception if certain resources are required and are not available:
-
NoMemoryError, if memory could not be allocated -
SystemStackError, if we have run out of stack to execute a subroutine (either Ruby VM stack or underlying C machine stack)
The execution raising process is slightly modified for these exceptions:
- A single global instance of
NoMemoryErrorandSystemStackErroris created by the Ruby VM at initialization and used henceforth. Ruby does not create new instances when raising these exceptions. - When the global instance of SystemStackError is raised (by the Ruby VM or otherwise):
- During the creation phase, it will be used directly, rather than being treated as a template (i.e.
.exception()will not be called), and it will not be assigned a backtrace even if requested - During the raising phase, it will not go through the usual
backtrace/set_backtraceprotocol, rather a simple string will be assigned to its internal backtrace state directly
When the Ruby VM is raising a NoMemoryError or, on some architectures, a machine-stack-related SystemStackError, the execution properties of the affected fiber are temporarily modified until the exception is actually raised - in particular while creating or raising the exception, or executing any "raise" event hooks as a result of the exception:
- Ruby will not re-raise the same type of exception again - further resource unavailable errors may be immediately fatal to the program
- The fiber will not do any thread interrupt processing
When an OS system call returns an error code, the Ruby core uses subclasses of SystemCallError to raise an exception.
Ruby's OS error codes are in the POSIX style, defined by names such as "EINVAL". On POSIX-like operating systems, these names are directly defined by the OS API, however they are just source-code-level symbols. In code execution, the names are translated to integer numbers (such a number is called an "errno"), and this mapping to numbers may differ between different operating systems and architectures. On non-POSIX operating systems with other error code schemes (such as Windows), Ruby translates the native error codes into appropriate POSIX-style error names.
Ruby defines pre-defines an exception class for each known error name as follows:
- A subclass of
SystemCallErroris created, and stored in the namespace of the top-level moduleErrno(e.g.Errno::EINVAL) - The (architecture-dependent) errno of that name is stored in the
Errnoconstant of that class's namespace (e.g.Errno::EINVAL::Errno - If two error names map to the same errno, then their corresponding constants in the
Errnonamespace will reference the same class (e.g. on architectures where "EAGAIN" and "EWOULDBLOCK" have the same errno,Errno::EAGAINandErrno::EWOULDBLOCKwill be the same class) - A subclass called
Errno::ENOERRORis always predefined with errno 0, and for error names which don't exist in the current architecture, their correspondingErrnoconstants will referenceErrno::ENOERROR
In the unlikely event that Ruby core or extension libraries generate a SystemCallError (using certain Ruby C APIs) for an errno that doesn't correspond to any name Ruby knows about, Ruby will at that point generate a subclass with a name corresponding to that number (e.g. for errno 41 the name may be Errno::E041) and raise an instance of it.
Ruby code can either by instantiate a subclass, or instantiate SystemCallError directly and specify the errno. If you instantiate SystemCallError directly with an errno that Ruby knows about, SystemCallError#initialize will actually change the instance's class to the corresponding SystemCallError subclass.
Ruby also supports matching SystemCallError and its subclasses to any object which supports the errno method, by way of SystemCallError::===.
A fatal exception is something you should never see, but in case it does come up, here is the explanation.
The Ruby core and extension libraries can raise an exception flagged as fatal, which they only do if they detect an unexpected problem in their own code and wish to initiate immediate termination. Ruby code can't raise a fatal exception directly.
Fatal exceptions are similar to other exceptions except:
- They can't be caught in
rescueclauses - If the current thread terminates with a fatal exception, it is propagated to the main thread
Fatal exceptions therefore typically result eventually in program termination, after all pertinent ensure clauses are run and other termination activites are completed.
Ruby provides another exception-style mechanism via Kernel#throw and Kernel#catch. These are roughly analagous to raise methods and the rescue expression.
Throw/catch is like using an exception as a kind of powerful "goto" statement, similar to "setjmp"/"longjmp" OS system calls with the ensure clause capability added. It works as follows:
- You call
Kernel#catch, which is analogous to a "goto" label or "setjmp" call, with an optional "tag" parameter - The catch "tag" is an arbitrary object that labels the catch invocation. If you don't specify one,
catchwill create one by allocating an emptyObjectinstance (via its allocator function) - You must pass a block to the
catchmethod, which it will invoke with the tag as a parameter - During the execution of the block, or of any code recursively called from within the block, you may call
Kernel#throwon the tag, which will act like an exception that is raised to be caught by thecatchmethod - You may pass a value to the
throwmethod, which will be returned by thecatchmethod. If thecatchmethod returns normally (without catching a throw), it will returnnil
throw-style exceptions can't be caught by rescue clauses, however they will cause ensure clauses to be invoked in the normal way. The tag specified in throw must be the exact same object as the tag labelling the corresponding catch. If you attempt to throw on a tag which has no corresponding catch in the stack, throw will raise a (normal) exception.
This section describes key elements of the exception API.
Exception#initialize [ <message> ]
Sets the exception's internal "message" state to <message>, or nil if not provided, and sets the internal "backtrace" state to nil.
Returns self.
Exception#to_s
Returns the internal "message" state of the exception as a String - if it's not a String, it will attempt to convert via to_str, or if failed, it will call to_s.
If the "message" state is nil or doesn't exist, returns the name of self's class.
Exception#message
Returns self.to_s.
Exception#backtrace
Returns internal "backtrace" state, or nil if it doesn't exist.
Exception#set_backtrace <bt>
Sets internal "backtrace" state to <bt>. If <bt> is a String, wraps it in an Array. Otherwise if <bt> is neither nil nor an Array of Strings, raises an error.
Returns <bt>.
Exception#== <obj>
Compares self to <obj>.
Returns true` if they are the same object
Accesses the internal "message" and "backtrace" states of self and obj:
- For
self, directly, assumingnilfor either if it doesn't exist - For
<obj>- if it's real class is the same asself's, directly, assumingnilfor either if it doesn't exist - For
<obj>- otherwise, via themessageandbacktracemethods, returningfalseif either method doesn't exist
If both selfs and <obj>'s "message" and "backtrace" == each other, returns true, otherwise returns false.
SystemCallError#initialize <errno> # Only valid when self is a SystemCallError, not a subclass
SystemCallError#initialize <mesg>, <errno> # Only valid when self is a SystemCallError, not a subclass
SystemCallError#initialize [ <mesg> ]
Determines an errno:
- If
self's class is not SystemCallError, usesself.class::Errno - If
<errno>is provided (if it's the first argument it must be a Fixnum), uses that - If
<errno>corresponds to one of the automatically generated subclasses of SystemCallError, changesself's class to that subclass - Otherwise, uses
nil
Constructs a message from <mesg>, if provided, and a description of the errno (as provided by an OS call)
Invokes super(message), sets the internal "errno" state to the determined errno, and returns self.
SystemCallError#errno
Returns the internal "errno" state of self, or nil if it doesn't exist.
SystemCallError::=== <obj>
Returns true if self is SystemCallError and <obj> is an instance of SystemCallError or its subclasses.
Returns false if <obj> is not a SystemCallError or subclass, and doesn't respond_to?(:errno).
Determines <obj>'s errno by accessing its internal "errno" state, or if that doesn't exist, via <obj>.errno.
Returns true if <obj>'s errno matches self::Errno, otherwise false.
Exception::exception *<args>
Equivalent to the default implementation of Class::new
Exception#exception *<args>
When invoked with no arguments, or one argument which is self, returns self.
Clones self (via the default implementation of Object#clone), invokes the default implementation of Exception#initialize(*<args>) on the clone, and returns it.
$!
Read-only. When accessed, returns the current exception context, or nil if none.
$@
When accessed, returns the result of invoking backtrace on the exception context (or nil if no exception context).
When assigned, invokes set_backtrace on the exception context with the assigned value (raises an error if no exception context).
Kernel::raise [ <template> ], [ <argument> ], [ <backtrace> ]
private Kernel#raise [ <template> ], [ <argument> ], [ <backtrace> ]
Kernel::fail [ <template> ], [ <argument> ], [ <backtrace> ]
private Kernel#fail [ <template> ], [ <argument> ], [ <backtrace> ]
These methods create and raise an exception, as described above in Raising Exceptions.
Kernel::warn <string>
private Kernel#warn <string>
Writes <string> to standard error unless warnings have been disabled (warning level 0). Returns nil.
Kernel::catch [ <tag> ] <block>
private Kernel#catch [ <tag> ] <block>
If <tag> is provided, it is used as the tag for the catch method, otherwise a new Object will be allocated to be used as the tag. Invokes <block> with the tag as an argument.
If <block> completed normally, returns its value. If the catch method caught its thrown tag, returns a value as per the arguments to Kernel#throw when its tag was thrown.
Kernel::throw <tag>, [ <value> ]
private Kernel#throw <tag>, [ <value> ]
If no Kernel#catch invocation is present on the stack corresponding to the given <tag>, raises an error.
Throws the tag as a quasi-exception to be caught by the corresponding catch invocation. If <value> is provided, the corresponding <catch> invocation will return it, otherwise it will return nil.
A Ruby Language Reference
Copyright © by Michael Hore, 2016.
Introduction to This Document
Ruby Elements
- Classes and Modules
- Methods
- Blocks, Procs and Lambdas
- Execution Context and Closures
- Variables, Constants and Namespaces
- Types and Literals
- Ruby Expressions
- Operators
Syntax Grammar
Exceptions and Throw
Ruby Sourcefiles and Libraries
Multi Threading
Execution and Lifecycle