Деструктор объекта в Ruby (define_finalizer)

За удаление объектов в Ruby отвечает сборщик мусора. Момент когда будет уничтожен объект не известен, но произойдет это только после того как не останется ни одной ссылки на данный объект. Исходя из этого, прямой поддержки деструктора, в типичном его проявлении, в Ruby нет.

Однако мы можем подписаться на удаление любого объекта посредством метода ObjectSpace#define_finalizer, передав объект класса Proc, который будет вызван после уничтожения объекта.

Вот как мог бы выглядеть деструктор в Ruby

class Bar
        def initialize
                ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)
        end
       
        def Bar.finalize(id)
                puts "Object #{id} dying at #{Time.new}"
        end
end
b = Bar.new

Потребность в объявлении деструктора появляется когда мы хотим неявно высвободить ресурсы занятые в процессе работы объекта, попробуем приспособить описанную выше технику для симуляции деструктора в Ruby, это в какой то степени позволит нам писать свои классы в духе RAII.

Метод Bar.finalize будет вызван после того как объект bar уже уничтожен, однако те объекты которыми он владеет, на момент вызова finalize еще живы и стоят в очереди на уничтожение (при условии что на них никто больше не ссылается). Перепишем класс Bar следующим образом: в конструкторе мы создадим файл, который необходим удалить при разрушении объекта bar. Имя файла запомним в переменной объекта:

require 'fileutils'

class Bar
        def initialize(file_name)
                @file_name = file_name.dup # запоминаем имя файла
                FileUtils.touch(@file_name) # создаем файл
        end
end

bar = Bar.new('temp.txt')

Теперь задача состоит в том как нам получить доступ в @file_name из деструктора. ObjectSpace#define_finalizer вторым параметром принимает объект типа Proc. Идея состоит в том чтобы продублировать имя файла и сохранить его в переданном объекте Proc, вот законченный вариант кода:

require 'fileutils'

class Bar
        def initialize(file_name)
                @file_name = file_name.dup # запоминаем имя файла
                FileUtils.touch(@file_name) # создаем файл
                ObjectSpace.define_finalizer(self, Bar.finilazer(@file_name))
        end     

        def Bar.finilazer(file_name)
                lambda {
                        puts "Remove file '#{file_name}'"
                        FileUtils.rm(file_name) # удалим файл
                }
        end
end

b = Bar.new('temp.txt')

Данное решение можно обобщить, например в виде мексина, но оставим реализацию на усмотрение читателя, возможно вы захотите поделится и с нами.

// UPDATE 17.10.2008

Нашел в загашнеке продакшен код с использованием RAII:

require 'win32ole'

module EDA
  class Caddy
    class Design < EDA::Design
      attr_reader :connection
     
      def initialize(eda, path)
        super(eda)
        openConnection(path)
      end
     
      def openConnection(path)
        Comlib.checkFileOrError(path)
        @connection = WIN32OLE.new('ADODB.Connection')
        @connection.Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=#{path};Jet OLEDB:Database Password=xxxxx;")
        ObjectSpace.define_finalizer(self, Design.finalize(@connection))       
      end
      private :openConnection
     
      def self.finalize(connection)
        lambda {
          connection.Close
        }
      end     
    end
  end
end