Lua Cleanup Patterns
A cleanup pattern is a helper that opens a resource, runs a body of code, and always closes the resource. The close happens on the success path and on the error path alike.
The helper closes on the success path
The body runs, the close is counted, and the helper returns ok.
local log = {}
local function open(name)
return setmetatable({name = name}, {__close = function(v) log[#log + 1] = v.name end})
end
local function with(name, body)
local outcome
do
local r <close> = open(name)
local ok, err = pcall(body, r)
if ok then outcome = "ok" else outcome = err end
end
return outcome .. ":" .. #log
end
print(with("tap", function(r) print(r.name) end))
print(table.concat(log, ","))The helper closes on the error path
The body fails, the close still runs, and the program continues.
local log = {}
local function open(name)
return setmetatable({name = name}, {__close = function(v) log[#log + 1] = v.name end})
end
local function with(name, body)
local outcome
do
local r <close> = open(name)
local ok, err = pcall(body, r)
if ok then outcome = "ok" else outcome = err end
end
return outcome .. ":" .. #log
end
print(with("lamp", function() error("fail", 0) end))
print(table.concat(log, ","))The close sees no error after a guarded body
pcall turns the body error into a value, so the scope ends cleanly.
local log = {}
local function open(name)
return setmetatable({name = name}, {__close = function(v, err) log[#log + 1] = v.name .. ":" .. tostring(err) end})
end
local function with(name, body)
local outcome
do
local r <close> = open(name)
local ok, err = pcall(body, r)
if ok then outcome = "ok" else outcome = err end
end
return outcome .. ":" .. #log
end
print(with("lamp", function() error("fail", 0) end))
print(log[1])Example
local log = {}
local function open(name)
return setmetatable({name = name}, {__close = function(v) log[#log + 1] = v.name end})
end
local function with(name, body)
local outcome
do
local r <close> = open(name)
local ok, err = pcall(body, r)
if ok then outcome = "ok" else outcome = err end
end
return outcome .. ":" .. #log
end
print(with("door", function() print("open") end))
print(with("window", function() error("stuck", 0) end))
print(table.concat(log, ","))Key points
- With helper: a function that opens a resource, runs a body, and returns the body outcome.
- Inner scope: the <close> variable sits in a small block, so it closes before the helper builds its return value.
- Guarded body: the body runs inside pcall, so its error becomes a value the helper can return.
- Success path: the same close runs when the body succeeds, and the helper returns ok.
- Error path: the same close runs when the body fails, and the helper returns the error message.
In this example, the first call runs its body and closes the door. The second call fails and still closes the window. The log shows both closes, in call order.
Note
the close sees no error here, because pcall already turned the body error into a return value.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
Why does the <close> variable sit inside a small block in the helper?