[DOC] Improve docs for ObjectSpace.define_finalizer

This commit is contained in:
Peter Zhu 2026-01-17 09:42:13 -05:00
parent 7e0e9984d0
commit 19450d85d6
Notes: git 2026-01-18 15:47:39 +00:00

87
gc.c
View File

@ -1709,63 +1709,68 @@ rb_gc_copy_finalizer(VALUE dest, VALUE obj)
/*
* call-seq:
* ObjectSpace.define_finalizer(obj, aProc=proc())
* ObjectSpace.define_finalizer(obj) {|id| ... } -> array
* ObjectSpace.define_finalizer(obj, finalizer) -> array
*
* Adds <i>aProc</i> as a finalizer, to be called after <i>obj</i>
* was destroyed. The object ID of the <i>obj</i> will be passed
* as an argument to <i>aProc</i>. If <i>aProc</i> is a lambda or
* method, make sure it can be called with a single argument.
* 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).
*
* The return value is an array <code>[0, aProc]</code>.
* 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 two recommended patterns are to either create the finaliser proc
* in a non-instance method where it can safely capture the needed state,
* or to use a custom callable object that stores the needed state
* explicitly as instance variables.
* The return value is an array <code>[0, callback]</code>, 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 initialize(data_needed_for_finalization)
* ObjectSpace.define_finalizer(self, self.class.create_finalizer(data_needed_for_finalization))
* end
*
* def self.create_finalizer(data_needed_for_finalization)
* proc {
* puts "finalizing #{data_needed_for_finalization}"
* }
* def define_final
* ObjectSpace.define_finalizer(self) do |id|
* puts "Running finalizer for #{id}!"
* end
* end
* end
*
* class Bar
* class Remover
* def initialize(data_needed_for_finalization)
* @data_needed_for_finalization = data_needed_for_finalization
* 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:
*
* class 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:
*
* class Foo
* class Finalizer
* def call(id)
* puts "finalizing #{@data_needed_for_finalization}"
* puts "Running finalizer for #{id}!"
* end
* end
*
* def initialize(data_needed_for_finalization)
* ObjectSpace.define_finalizer(self, Remover.new(data_needed_for_finalization))
* def define_final
* ObjectSpace.define_finalizer(self, Finalizer.new)
* end
* end
*
* Note that if your finalizer references the object to be
* finalized it will never be run on GC, although it will still be
* run at exit. You will get a warning if you capture the object
* to be finalized as the receiver of the finalizer.
*
* class CapturesSelf
* def initialize(name)
* ObjectSpace.define_finalizer(self, proc {
* # this finalizer will only be run on exit
* puts "finalizing #{name}"
* })
* end
* end
*
* Also note that finalization can be unpredictable and is never guaranteed
* Note that finalization can be unpredictable and is never guaranteed
* to be run except on exit.
*/