RAII en Ruby (ou comment gérer les ressources en Ruby)
-
03-07-2019 - |
Question
Je sais que c'est par conception que vous ne pouvez pas contrôler ce qui se produit lorsqu'un objet est détruit. Je suis également conscient de la définition d’une méthode de classe comme finaliseur.
Cependant, l'idiome ruby ??pour RAII de C ++ (les ressources sont initialisées dans le constructeur, fermées dans le destructeur)? Comment les personnes gèrent-elles les ressources utilisées dans les objets même lorsque des erreurs ou des exceptions se produisent?
L'utilisation de sure fonctionne:
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
mais les utilisateurs de la classe ne doivent pas oublier de faire tout le processus begin-rescue-guarantee chacha chaque fois que la méthode open doit être appelée.
Ainsi, par exemple, j'aurai la classe suivante:
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
Le resource_handle ne sera pas fermé si l'exception est causée par une autre classe et que le script se ferme.
Ou le problème est-il davantage lié au fait que je le fais encore - comme C ++?
La solution
Pour que les utilisateurs ne soient pas " obligés de se rappeler de faire tout le processus begin-rescue-assure chacha ". combinez rescue
/ assure
avec rendement
.
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
Le code client peut l’utiliser comme suit:
SomeResource.use(connection_string) do | resource |
resource.do_something
... # whatever else
end
# after this point resource has been .close()d
En fait, voici comment File.open
fonctionne - rendant la première réponse confuse au mieux (bien c’était à mes collègues de travail).
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
Autres conseils
Que diriez-vous de céder
une ressource à un bloc? Exemple:
File.open("testfile") do |f|
begin
# .. process
rescue
# .. handle error
end
end
Ou le problème est-il davantage lié au fait que je le fais encore - comme C ++?
Oui, cela se produit depuis que la désallocation de ressources C ++ est implicite pour tout ce qui se trouve sur la pile. Pile déroulée = ressource détruite = destructeurs appelés et à partir de là des choses peuvent être libérées. Comme Ruby n’a pas de destructeurs, il n’ya pas de "faire cela quand tout le reste est fait avec". place puisque la collecte des déchets peut être retardée de plusieurs cycles de l'endroit où vous vous trouvez. Vous avez des finaliseurs, mais ils sont appelés "dans les limbes". (tout ne leur est pas accessible) et ils sont appelés sur GC.
Par conséquent, si vous détenez un descripteur sur une ressource qu'il vaut mieux libérer, vous devez le divulguer explicitement. En effet, le bon idiome pour gérer ce genre de situation est
def with_shmoo
handle = allocate_shmoo
yield(handle)
ensure
handle.close
end
Voir http://www.rubycentral.com/pickaxe/tut_exceptions.html
En Ruby, vous utiliseriez une instruction assure
:
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
Ceci sera familier aux utilisateurs de Python, Java ou C # en ce sens que cela fonctionne comme try / catch / finally.