Closing Files and Error Handling
Close file handles properly and handle io errors with pcall.
Closing Files and Error Handling is a free Lua Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Always Close Files
Every file opened with io.open must be closed with f:close(). Unclosed files leak file descriptors. Lua's GC will eventually close them, but you can exhaust OS file limits before GC runs. Always close in the function that opened the file.
local function readFile(path)
local f, err = io.open(path, "r")
if not f then return nil, err end
local content = f:read("a")
f:close() -- always close
return content
end
local data, err = readFile("config.txt")
if not data then print("Error:", err) endpcall for IO Safety
Wrap file operations in pcall to catch unexpected errors (disk full, permission denied mid-write). This ensures you handle errors gracefully rather than crashing the program.
local function safeCopy(src, dst)
local ok, err = pcall(function()
local f = io.open(src, "rb")
if not f then error("Cannot open source: " .. src) end
local content = f:read("a")
f:close()
local g = io.open(dst, "wb")
if not g then error("Cannot open dest: " .. dst) end
g:write(content)
g:close()
end)
return ok, err
end
local ok, msg = safeCopy("input.txt", "output.txt")
if not ok then print("Failed:", msg) endChecking Write Success
On systems with full disks or permission issues, f:write() can fail even after io.open succeeded. Check the return value of write operations in critical paths.
local f = io.open("important.txt", "w")
if not f then
print("Cannot create file")
return
end
local ok, err = f:write("critical data\n")
if not ok then
print("Write failed:", err)
f:close()
return
end
f:close()
print("Saved successfully")finally Pattern with pcall
Lua lacks a finally block, but you can simulate it with pcall. Wrap the risky code in pcall; after pcall returns (success or failure), run cleanup code unconditionally before re-raising or handling the error.
local function withFile(path, mode, fn)
local f, err = io.open(path, mode)
if not f then return nil, err end
local ok, result = pcall(fn, f)
f:close() -- always close, even on error
if not ok then
return nil, result
end
return result
end
local content, err = withFile("data.txt", "r", function(f)
return f:read("a")
end)
print(content or err)Error Messages from io.open
io.open returns nil plus a system error message and error code. The message includes the path and the OS error reason ("No such file", "Permission denied", etc.). Always include the error message in your error report to the user.
local paths = {"config.txt", "missing.txt", "/root/secret.txt"}
for _, path in ipairs(paths) do
local f, err, code = io.open(path, "r")
if f then
print(path, "-> ok, size:", f:seek("end"))
f:close()
else
print(path, "-> ERROR:", err, "(code "..tostring(code)..")")
end
endFile Handle as Object
A Lua file handle is a userdata with methods. When the handle is garbage collected, the file is automatically closed. However, don't rely on GC for timely closure — always close explicitly. You can check if a file is closed with f:read() — it errors on a closed handle.
local f = io.open("test.txt", "w")
f:write("test\n")
-- Explicit close
f:close()
-- After close, operations fail
local ok, err = pcall(function()
f:read() -- attempt to use closed file
end)
print(ok, err) -- false [closed file]Safe File Processing Template
A reusable template for safe file processing: open with error check, process with pcall, close in all cases. Return results and errors consistently using the nil+message convention.
local function processFile(path, processor)
local f, err = io.open(path, "r")
if not f then return nil, "open failed: " .. err end
local ok, result = pcall(processor, f)
f:close()
if not ok then
return nil, "processing failed: " .. result
end
return result
end
local lineCount, err = processFile("data.txt", function(f)
local n = 0
for _ in f:lines() do n = n + 1 end
return n
end)
print(lineCount or err)os.remove and os.rename
os.remove(path) deletes a file. os.rename(old, new) renames or moves a file. Both return true on success or nil, errMessage on failure. These are the primary file system manipulation functions in standard Lua.
-- Delete a file
local ok, err = os.remove("temp.txt")
if not ok then print("Remove failed:", err) end
-- Rename / move
ok, err = os.rename("old_name.txt", "new_name.txt")
if not ok then print("Rename failed:", err) end
-- Atomic update pattern
os.rename("config.new", "config.txt")io.close vs f:close
io.close(f) is equivalent to f:close(). io.close() with no argument closes the default output file. In most code, use f:close() for clarity.
local f = io.open("test.txt", "w")
f:write("hello\n")
-- Both are equivalent:
-- f:close()
io.close(f)
-- io.close() with no arg closes default output
io.output("out.txt")
io.write("to file\n")
io.close() -- closes out.txt, restores stdoutRobust File Copy
A production-quality file copy with error handling at every step: open source, open destination, copy in chunks, handle write errors, close both files.
local function copyFile(src, dst, chunkSize)
chunkSize = chunkSize or 65536
local fsrc, err1 = io.open(src, "rb")
if not fsrc then return nil, err1 end
local fdst, err2 = io.open(dst, "wb")
if not fdst then fsrc:close(); return nil, err2 end
local ok = true
local chunk = fsrc:read(chunkSize)
while chunk and ok do
local w, werr = fdst:write(chunk)
if not w then ok = false; err2 = werr end
chunk = fsrc:read(chunkSize)
end
fsrc:close(); fdst:close()
if not ok then os.remove(dst); return nil, err2 end
return true
endQuick Check
What is the best way to ensure a file is always closed in Lua, even if an error occurs?
Recap: Closing and Error Handling
Summary:
- Always call
f:close()explicitly — don't rely on GC - Wrap IO in pcall for robust error handling
- Use the "open → pcall → close" template
os.remove/os.renamefor file system operations- Check write return values in critical code
Frequently asked questions
Is the “Closing Files and Error Handling” lesson free?
Yes — the full text of “Closing Files and Error Handling” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.
What will I learn in “Closing Files and Error Handling”?
Close file handles properly and handle io errors with pcall. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Lua Academy?
No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Closing Files and Error Handling” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Lua Academy lesson?
Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Opening Files with io.open
- Reading File Contents
- Writing and Appending to Files
- Closing Files and Error Handling