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