ensure, at_exit and cleanup
ensure
ensure is the part of a begin that always runs: after a normal return, after a rescue, and while an exception is still on its way out. The exception is not swallowed; the cleanup happens and the error continues:
begin
raise "x"
ensure
puts :done
end
ruby -e 'begin; raise "x"; ensure; puts :done; end'
-e:1:in '<main>': x (RuntimeError)
done
The same works in a method, and it also runs on an early return, which is what makes it reliable for cleanup:
ruby -e 'def f; return 1; ensure; puts :clean; end; p f'
clean
1
A block that closes
The standard library already uses ensure in the methods that open something. File.open (the file API, later in lesson 119) with a block closes the file when the block ends, even if it raises:
ruby -e 'File.open("f.txt", "w") { |f| f.puts "x" }; p File.read("f.txt")'
"x\n"
Use the block form even when nothing can fail. The version that assigns f = File.open(...) needs its own ensure with f.close, and the one time the close is forgotten the file stays open until the process ends. The same pattern covers a database connection, a lock and a temporary file.
at_exit
at_exit registers a block that runs when the program ends, after the last line and after the handler for an uncaught exception:
ruby -e 'at_exit { puts "bye" }; puts "hi"'
hi
bye
It is the right place for a summary line or a temporary file that must go away whatever happened. It is the wrong place for anything that matters: the block does not run when the process is killed with kill -9, and several handlers run in reverse order of registration.
Temporary directories
Dir.mktmpdir (the directory API is in lesson 120, later) takes a block and removes the directory afterwards, which makes it the cleanest scratch space for a test or a conversion:
ruby -e 'require "tmpdir"; dir = nil; Dir.mktmpdir { |d| dir = d; p File.directory?(d) }; p File.exist?(dir)'
true
false
The directory exists inside the block and is gone after it, even when the block raises. Tempfile from the standard library does the same for one file, and neither needs a name a test has to invent.
What to watch for
A return inside an
ensure overrides the exception or the value the
method was about to return. It is legal, it is almost always a bug, and
Ruby will warn about it in some versions.
ensure is not a place for a long job: it runs
while an exception is in flight, and an error raised there replaces the
original. Keep it to the close, the delete and the unlock.
Next: warn, warnings and log levels
Article author: Arthur Isaev