RAII в Ruby (или как управлять ресурсами в Ruby)
-
03-07-2019 - |
Вопрос
Я знаю, что так задумано, что вы не можете контролировать, что происходит, когда объект уничтожается.Мне также известно об определении некоторого метода класса в качестве финализатора.
Однако является ли рубиновая идиома для RAII С++ (ресурсы инициализируются в конструкторе, закрываются в деструкторе)?Как люди управляют ресурсами, используемыми внутри объектов, даже если случаются ошибки или исключения?
С использованием гарантировать работает:
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
но пользователи класса нужно не забыть сделать всю чачу «начать-спасти-обеспечить» каждый раз, когда необходимо вызвать открытый метод.
Например, у меня будет следующий класс:
class SomeResource
def initialize(connection_string)
@resource_handle = ...some mojo here...
end
def do_something()
begin
@resource_handle.do_that()
...
rescue
...
ensure
end
def close
@resource_handle.close
end
end
Resource_handle не будет закрыт, если исключение вызвано каким-либо другим классом и скрипт завершится.
Или проблема в том, что я все еще делаю это слишком похоже на C++?
Решение
Чтобы пользователи не "нужно не забыть сделать всю чачу «начать-спасти-обеспечить»"объединить rescue
/ensure
с yield
.
class SomeResource
...
def SomeResource.use(*resource_args)
# create resource
resource = SomeResource.new(*resource_args) # pass args direct to constructor
# export it
yield resource
rescue
# known error processing
...
ensure
# close up when done even if unhandled exception thrown from block
resource.close
end
...
end
Клиентский код может использовать его следующим образом:
SomeResource.use(connection_string) do | resource |
resource.do_something
... # whatever else
end
# after this point resource has been .close()d
На самом деле вот так File.open
работает - первый ответ в лучшем случае сбивает с толку (ну, это было мой коллеги по работе).
File.open("testfile") do |f|
# .. process - may include throwing exceptions
end
# f is guaranteed closed after this point even if exceptions are
# thrown during processing
Другие советы
Как насчет yield
включение ресурса в блок?Пример:
File.open("testfile") do |f|
begin
# .. process
rescue
# .. handle error
end
end
Или проблема в том, что я все еще делаю это слишком похоже на C++?
Да, это так, поскольку в C++ освобождение ресурсов происходит неявно для всего, что находится в стеке.Стек размотан = ресурс уничтожен = вызваны деструкторы, и оттуда все можно освободить.Поскольку в Ruby нет деструкторов, нет места «сделать это, когда все остальное будет сделано», поскольку сбор мусора может быть отложен на несколько циклов по сравнению с тем местом, где вы находитесь.У вас есть финализаторы, но они называются «в подвешенном состоянии» (им доступно не все) и они вызываются на GC.
Поэтому, если вы удерживаете дескриптор какого-то ресурса, который лучше освободить, вам необходимо освободить его явно.Действительно, правильная идиома для решения такой ситуации —
def with_shmoo
handle = allocate_shmoo
yield(handle)
ensure
handle.close
end
Видеть http://www.rubycentral.com/pickaxe/tut_Exceptions.html
В Ruby вы бы использовали ensure
заявление:
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
Это будет знакомо пользователям Python, Java или C#, поскольку оно работает как try/catch/finally.