RAII en Ruby (o cómo administrar recursos en Ruby)
-
03-07-2019 - |
Pregunta
Sé que es por diseño que no puedes controlar lo que sucede cuando se destruye un objeto. También soy consciente de definir algún método de clase como finalizador.
Sin embargo, ¿es el lenguaje ruby ??para RAII de C ++ (los recursos se inicializan en el constructor, se cierran en el destructor)? ¿Cómo administran las personas los recursos utilizados dentro de los objetos, incluso cuando ocurren errores o excepciones?
El uso de garantizar funciona:
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
pero los usuarios de la clase tienen que recordar hacer todo el comenzar-rescate-asegurar-chacha cada vez que se debe llamar al método abierto.
Por ejemplo, tendré la siguiente clase:
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
El resource_handle no se cerrará si la excepción es causada por alguna otra clase y el script se cierra.
¿O el problema es que aún estoy haciendo esto también como en C ++?
Solución
Para que los usuarios no " tengan que acordarse de hacer todo el comenzar-rescate-asegurar chacha " Combine rescue
/ asegúrese
con rendimiento
.
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
El código del cliente puede usarlo de la siguiente manera:
SomeResource.use(connection_string) do | resource |
resource.do_something
... # whatever else
end
# after this point resource has been .close()d
De hecho, así es como funciona File.open
, lo que hace que la primera respuesta sea confusa en el mejor de los casos (bueno, fue para mis compañeros de trabajo).
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
Otros consejos
¿Qué tal si produce
un recurso a un bloque? Ejemplo:
File.open("testfile") do |f|
begin
# .. process
rescue
# .. handle error
end
end
¿O el problema es que aún estoy haciendo esto también como en C ++?
Sí, es así porque en C ++ la desasignación de recursos ocurre implícitamente para todo en la pila. Pila desenrollada = recurso destruido = destructores llamados y desde allí se pueden liberar cosas. Dado que Ruby no tiene destructores, no hay " hacer eso cuando todo lo demás se hace con " lugar ya que la recogida de grabage puede retrasarse varios ciclos desde donde se encuentre. Tiene finalizadores, pero se llaman " en el limbo " (no todo está disponible para ellos) y se les llama en GC.
Por lo tanto, si tiene un identificador para algún recurso que es mejor liberar, debe liberarlo explícitamente. De hecho, el idioma correcto para manejar este tipo de situación es
def with_shmoo
handle = allocate_shmoo
yield(handle)
ensure
handle.close
end
Consulte http://www.rubycentral.com/pickaxe/tut_exceptions.html
En Ruby, utilizarías una declaración de asegúrese
:
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
Esto será familiar para los usuarios de Python, Java o C #, ya que funciona como prueba / captura / finalmente.