Exception
What's an Exception?
From the documentation for Ruby's Exception
class:
Descendants of class Exception are used to communicate between Kernel#raise and
rescue
statements inbegin ... end
blocks. Exception objects carry information about the exception – its type (the exception's class name), an optional descriptive string, and optional traceback information.
To demonstrate, we'll use Ruby and the Ruby Interactive Shell, irb
, beginning with their versions:
p `ruby --version`.chomp
"ruby 2.6.3p62 (2019-04-16 revision 67580) [x64-mingw32]"
p `irb --version`.chomp
"irb 1.0.0 (2018-12-18)"
Contents
The Basics
Creating Exceptions
Create a new exception:
x = Exception.new
p x
#<Exception: Exception>
Create a new exception with a message:
x = Exception.new('Boo!')
p x
#<Exception: Boo!>
Create a new exception with a non-string argument (which is then converted to a string):
x = Exception.new(:symbol)
p x
#<Exception: symbol>
x = Exception.new(Range.new(0, 4))
p x
#<Exception: 0..4>
Exception.exception
behaves the same as Exception.new
:
x = Exception.exception
p x
#<Exception: Exception>
x = Exception.exception('Boo!')
p x
#<Exception: Boo!>
Create a new exception of the same class as an existing exception, but with a different message:
x = Exception.exception('x message')
p x
#<Exception: x message>
y = x.exception('y message')
p y
#<Exception: y message>
p x.__id__ == y.__id__
false
Method :exception
returns self
when passed no argument:
x = Exception.new
p x
#<Exception: Exception>
y = x.exception
p y
#<Exception: Exception>
p x.__id__ == y.__id__
true
Method :exception
returns self
when passed self
as an argument:
x = Exception.new
p x
#<Exception: Exception>
y = x.exception(x)
p y
#<Exception: Exception>
p x.__id__ == y.__id__
true
Method :exception
returns a new exception with a new message when passed anything else as an argument:
x = Exception.new
p x
#<Exception: Exception>
y = x.exception(:Boo)
p y
#<Exception: Boo>
p x.__id__ == y.__id__
false
Examining Exceptions
Get a string showing an exception's class and message:
x = Exception.new('Boo!')
p x.inspect
"#<Exception: Boo!>"
Get an exception's message:
p x.to_s
"Boo!"
Get an exception's message:
p x.message
"Boo!"
For an exception with no message, method :message
returns the class name.
x = Exception.new
p x.message
"Exception"
Rescued Exceptions
Rescue an exception:
rescued = nil
begin
raise Exception.new('Boo!')
rescue Exception => x
rescued = x
end
Show its class and message:
p rescued.class
Exception
p rescued.message
"Boo!"
Method :backtrace
returns an array of strings. This one is large:
backtrace = rescued.backtrace
p backtrace.class
Array
p backtrace.size
20
The whole thing:
puts backtrace
irb_input:145:in `irb_binding'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/workspace.rb:85:in `eval'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/workspace.rb:85:in `evaluate'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/context.rb:385:in `evaluate'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:493:in `block (2 levels) in eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:647:in `signal_status'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:490:in `block in eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:232:in `loop'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:231:in `catch'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:489:in `eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:428:in `block in run'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:427:in `catch'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:427:in `run'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:383:in `start'
C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
C:/Ruby26-x64/bin/irb.cmd:31:in `load'
C:/Ruby26-x64/bin/irb.cmd:31:in `<main>'
Set backtrace, in this case to an empty array:
rescued.set_backtrace([])
puts rescued.backtrace
The original backtrace information is still available via method :backtrace_locations
, but the result is an array of Thread::Backtrace::Location
, not an array of String
.
p rescued.backtrace_locations.first.class
Thread::Backtrace::Location
puts rescued.backtrace_locations
irb_input:145:in `irb_binding'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/workspace.rb:85:in `eval'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/workspace.rb:85:in `evaluate'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/context.rb:385:in `evaluate'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:493:in `block (2 levels) in eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:647:in `signal_status'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:490:in `block in eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:232:in `loop'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:231:in `catch'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:489:in `eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:428:in `block in run'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:427:in `catch'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:427:in `run'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:383:in `start'
C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
C:/Ruby26-x64/bin/irb.cmd:31:in `load'
C:/Ruby26-x64/bin/irb.cmd:31:in `<main>'
Equality
Two exceptions are equal under :==
if they have the same class, message, and backtrace.
All the same:
clone = rescued.clone
p rescued.class == x.class
true
p rescued.message == x.message
true
p rescued.backtrace == x.backtrace
true
p rescued == clone
true
Different class:
x = RuntimeError.new(rescued.message)
x.set_backtrace(rescued.backtrace)
p rescued.class == x.class
false
p rescued.message == x.message
true
p rescued.backtrace == x.backtrace
true
p rescued == x
false
Different message:
x = rescued.exception('Foo!')
x.set_backtrace(rescued.backtrace)
p rescued.class == x.class
true
p rescued.message == x.message
false
p rescued.backtrace == x.backtrace
true
p rescued == x
false
Different backtrace:
x = clone.exception
backtrace = rescued.backtrace_locations.map { |x| x.to_s }
x.set_backtrace(backtrace)
p rescued.class == x.class
true
p rescued.message == x.message
true
p rescued.backtrace == x.backtrace
false
p rescued == x
false
More Methods
Method :cause
When an exception is raised, method :cause
returns the previously-raised exception, as shown by this code from Starr Horne:
def fail_and_reraise
raise NoMethodError
rescue
raise RuntimeError
end
begin
fail_and_reraise
rescue => e
puts "#{ e } caused by #{ e.cause }"
end
RuntimeError caused by NoMethodError
Method :to_tty?
Determine whether an Exception will be written to a tty:
p Exception.to_tty?
false
Global Variables
In a rescue
, ensure
, at_exit
, or END
block, the already-raised but not-yet-handled exception is accessible via global variables. $!
has the raised exception, and $@
has its backtrace
begin
raise Exception.new('Boo!')
rescue Exception => x
p $!
puts $@
end
#<Exception: Boo!>
irb_input:267:in `irb_binding'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/workspace.rb:85:in `eval'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/workspace.rb:85:in `evaluate'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/context.rb:385:in `evaluate'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:493:in `block (2 levels) in eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:647:in `signal_status'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:490:in `block in eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:232:in `loop'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:231:in `catch'
C:/Ruby26-x64/lib/ruby/2.6.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:489:in `eval_input'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:428:in `block in run'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:427:in `catch'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:427:in `run'
C:/Ruby26-x64/lib/ruby/2.6.0/irb.rb:383:in `start'
C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
C:/Ruby26-x64/bin/irb.cmd:31:in `load'
C:/Ruby26-x64/bin/irb.cmd:31:in `<main>'
Built-In Subclasses
See the built-in subclasses of class Exception
, at its documentation.
Custom Exceptions
Many Rubyists believe that it's good practice to use custom exceptions, rather than the built-in exceptions. Read all about it: