Preview Ruby 4.1 is a preview. It can change before the final release. Switch to Ruby 4.0 →
ObjectSpace.define_finalizer
static VALUE
define_final(int argc, VALUE *argv, VALUE os)
{
VALUE obj, block;
rb_scan_args(argc, argv, "11", &obj, &block);
if (argc == 1) {
block = rb_block_proc();
}
if (rb_callable_receiver(block) == obj) {
rb_warn("finalizer references object to be finalized");
}
return rb_define_finalizer(obj, block);
}
Adds a new finalizer for obj that is called when obj is destroyed by the garbage collector or when Ruby shuts down (which ever comes first).
With a block given, uses the block as the callback. Without a block given, uses a callable object finalizer as the callback. The callback is called when obj is destroyed with a single argument id which is the object ID of obj (see Object#object_id).
The return value is an array [0, callback], where callback is a Proc created from the block if one was given or finalizer otherwise.
Note that defining a finalizer in an instance method of the object may prevent the object from being garbage collected since if the block or finalizer refers to obj then obj will never be reclaimed by the garbage collector. For example, the following script demonstrates the issue:
class Foo def define_final ObjectSpace.define_finalizer(self) do |id| puts "Running finalizer for #{id}!" end end end obj = Foo.new obj.define_final
There are two patterns to solve this issue:
-
Create the finalizer in a non-instance method so it can safely capture the needed state:
Exampleclass Foo def define_final ObjectSpace.define_finalizer(self, self.class.create_finalizer) end def self.create_finalizer proc do |id| puts "Running finalizer for #{id}!" end end end
-
Use a callable object:
Exampleclass Foo class Finalizer def call(id) puts "Running finalizer for #{id}!" end end def define_final ObjectSpace.define_finalizer(self, Finalizer.new) end end
Note that finalization can be unpredictable and is never guaranteed to be run except on exit.