Eli Sadoff
Eli Sadoff

Reputation: 7308

How do I raise a fatal exception ruby?

Ruby has a fatal exception, but there is no guidance on how to raise it and I cannot figure it out. How do I raise a fatal exception in Ruby?

Upvotes: 8

Views: 2076

Answers (3)

vladimir ulianitsky
vladimir ulianitsky

Reputation: 136

For MRI, you can reproduce the way fatal is defined via ffi and convert C API VALUEs with fiddle, but note that simply raising it raise fatal, '...' is like calling rb_raise(rb_eFatal, ...), not rb_fatal(...), you can see they use different tags here, the last cannot be rescued (rescue fatal won't work too)

require 'ffi'
require 'fiddle'

module RbFFI
  extend FFI::Library
  ffi_lib FFI::CURRENT_PROCESS
end

RbFFI.attach_function :rb_define_class, [:string, :ulong], :ulong
RbFFI.attach_function :rb_raise, [:ulong, :string, :varargs], :void
RbFFI.attach_function :rb_fatal, [:string, :varargs], :void

rb_eException = Fiddle.dlwrap Exception # Just a hacky way to convert get VALUE representation of Ruby object
rb_eFatal = RbFFI.rb_define_class('fatal', rb_eException)
fatal = Fiddle.dlunwrap(rb_eFatal) # Just a hacky way to convert get Ruby object from VALUE representation

at_exit do
  puts '-----------------'
  puts 'at_exit'
end

def test(name, &block)
  puts '-----------------'
  puts "test: #{name}"
  begin
    block.call
  rescue Exception => e
    puts "rescued #{e.message} (#{e.class})"
  ensure
    puts "ensure: #{name}"
  end
  puts "done: #{name}"
end

test('1') { RbFFI.rb_raise rb_eFatal, 'rb_raise rb_eFatal' }

test('2') { raise fatal, 'raise fatal' }

test('3') { RbFFI.rb_fatal 'rb_fatal' }

produces

-----------------
test: 1
rescued rb_raise rb_eFatal (fatal)
ensure: 1
done: 1
-----------------
test: 2
rescued raise fatal (fatal)
ensure: 2
done: 2
-----------------
test: 3
ensure: 3
-----------------
at_exit
/path/to/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/ffi-1.16.3/lib/ffi/variadic.rb:47:in `invoke': rb_fatal (fatal)
    from /path/to/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/ffi-1.16.3/lib/ffi/variadic.rb:47:in `call'
    from /path/to/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/ffi-1.16.3/lib/ffi/variadic.rb:62:in `rb_fatal'
    from fatal.rb:38:in `block in <main>'
    from fatal.rb:26:in `test'
    from fatal.rb:38:in `<main>'

Upvotes: 0

akuhn
akuhn

Reputation: 27813

Sure you can.

Try this

FatalError = ObjectSpace.each_object(Class).find { |klass| klass < Exception && klass.inspect == 'fatal' }

And then

raise FatalError.new("famous last words")

How does this work?

  • fatal is an internal class without associated top-level constant
  • ObjectSpace.each_object(Class) enumerates over all classes
  • find { ... } finds an exception class named "fatal"

NB though, despite its name fatal is not special, it can be rescued. If you are looking for a way to end your program maybe best call the global exit method?

begin
  raise FatalError.new
rescue Exception => e
  puts "Not so fatal after all..."
end

Upvotes: 12

Drenmi
Drenmi

Reputation: 8787

Short answer is, you can, but probably shouldn't. This exception is reserved for Ruby internals. It is effectively hidden to users by being a constant with an all lowercase identifier. (Ruby won't do a constant lookup unless the identifier starts with an uppercase character.)

fatal
NameError: undefined local variable or method `fatal' for main:Object

The same is true when using Object#const_get:

Object.const_get(:fatal)
NameError: wrong constant name fatal

If this exception class was intended for us to use, then it would be readily available, and not hidden away.

Upvotes: 1

Related Questions