About Ruby Class Exception

Burdette Lamar - Sep 6 '19 - - Dev Community

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 in begin ... 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:

More

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .