Reputation: 7308
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
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
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 constantObjectSpace.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
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