Lua 5.4 To-Be-Closed Variables: Automatic Cleanup When Scope Ends
Use Lua 5.4's <close> attribute and __close metamethod to close files and other resources automatically when they leave scope, even after errors.
18 Sept 2026, 08:30 UTC

The problem: cleanup that depends on memory
Imagine a Lua function that opens a log file, reads several lines, and then fails partway through parsing. If your f:close() call sits after the parsing code, an error skips it entirely. Wrapping the block in pcall catches the error, but you still have to close the handle in every success and failure branch. In an embedded script that runs for hours or days, each missed close leaves a file descriptor or socket open until the process exits.
What to-be-closed variables do
Lua 5.4 introduces the attribute for local variables. When the variable leaves scope, Lua invokes its __close metamethod. This works for file handles returned by io.open, tables you define yourself, and userdata created by C modules. The cleanup runs whether the block ends normally or an error propagates through it. Multiple to-be-closed locals close in reverse order of declaration, which mirrors stack unwinding.
The term scope exit means the end of the block where the variable was declared, such as the end of a function, an if branch, or a loop iteration. A metamethod is a Lua function triggered by a specific event on a table or userdata; __close is the event for resource cleanup.
Worked example: a handle that closes itself
Save this as close_demo.lua and run it with Lua 5.4 or later:
local function tracked_resource(name)
return setmetatable({label = name}, {
__close = function(self)
io.stderr:write("closed: " .. self.label .. "\n")
end,
})
end
local function process(should_fail)
local res = tracked_resource("temp-file")
print("using " .. res.label)
if should_fail then
error("simulated parse failure")
end
print("finished without error")
end
process(true)
Run it from a terminal with no special permissions:
lua close_demo.lua
If the runtime supports __close, you should see using temp-file, then closed: temp-file, followed by the error trace. The resource closed before the error continued up the stack.
To confirm your interpreter is new enough, run:
lua -v
Look for Lua 5.4 or a later version. You can also inspect a file handle directly:
lua -e "local f = io.open('/tmp/probe','w'); print(debug.getmetatable(f).__close ~= nil); f:close()"
A result of true means the handle carries a __close metamethod. Remove /tmp/probe afterward if you do not need it.
Trade-offs and version limits
Lua 5.1, Lua 5.2, and LuaJIT do not support . Code that uses it will fail to parse on those runtimes. If you ship a library across versions, detect the interpreter at load time and fall back to explicit pcall with f:close() in both branches.
There are two behavioral risks. First, if a __close metamethod raises its own error during unwinding, it can mask the original failure. Keep cleanup functions free of operations that can fail. Second, assigning nil to a to-be-closed variable before the block ends triggers __close immediately, so do not reuse the variable later in the same scope expecting the resource to remain open.
Where to use it today
Use local f = io.open(path) in new Lua 5.4 scripts and embedded modules whenever a handle should live only for one block. Review existing cleanup code for paths where an error bypasses close. On older runtimes, keep explicit close calls inside xpcall error handlers or duplicate them in success and failure branches until you can upgrade.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.