Using blocks for cleanup
This week we'll celebrate the block, one of Ruby's most powerful and beloved features!
Blocks are anonymous chunks of code that can be passed to methods and executed in a specific context. They are used for a wide variety of purposes, such as iteration, cleanup, callbacks and custom control flow.
Today we'll look at how blocks can be used for cleanup, i.e. to ensure that resources are properly released after use, even in the face of errors. This is a pattern commonly seen in Ruby's standard library:
File.open("example.txt", "w") do |file|
file.write("Hello, world!")
# If an error occurs here, the file will still be closed properly
raise "Something went wrong!"
end
require "net/http"
Net::HTTP.start("example.com") do |http|
response = http.get("/")
# If an error occurs here, the connection will still be closed properly
raise "Something went wrong!"
end
To implement this pattern, the method that takes a block uses ensure to guarantee that the cleanup code runs at the end of block execution:
def open_file(path, mode)
file = File.open(path, mode)
begin
yield(file)
ensure
file.close
end
end
# NOTE: Only works on hosts with lsof installed (e.g. Linux and macOS)
number_of_open_files_before_block = `lsof -p #{Process.pid} | wc -l`.strip.to_i
puts "Open files before: #{number_of_open_files_before_block}"
begin
open_file("example.txt", "w") do |file|
file.write("Hello, world!")
number_of_open_files_during_block = `lsof -p #{Process.pid} | wc -l`.strip.to_i
puts "Open files during block: #{number_of_open_files_during_block}"
raise "Something went wrong!"
end
rescue
end
number_of_open_files_after_block = `lsof -p #{Process.pid} | wc -l`.strip.to_i
puts "Open files after: #{number_of_open_files_after_block}"
History
Blocks have been a part of Ruby since its inception. They were inspired by blocks in Smalltalk and closures in Lisp and other functional languages.
The idea of using blocks together with ensure for cleanup has also been there from the very beginning, with e.g. IO.foreach using it all the way back in Ruby 1.0 in 1995.
The block form of File.open shipped in Ruby 1.2 in 1998.
Net::HTTP including the block form of Net::HTTP.start shipped in Ruby 1.4.4 in 2000.