CodeRigor

FREE LESSON

Files, errors & deterministic resource cleanup

Read files without losing empty or final lines, report open failures and close handles deterministically.

Before this lesson, complete lua-modules-errors, lua-control-functions.

This course runs standard Lua 5.5 with its standard library. Game engines, LuaJIT and embedded host APIs are outside this sequence.

Open files have a lifetime separate from the string or table values you compute from them. io.open returns a handle on success and a failure result when opening is impossible. Check the handle before reading. In this lab, an open failure is an expected API result, so normalize it to nil and the stable message open failed rather than returning an operating-system-dependent diagnostic.

Open, check, read
start / 3
Open, check, read

Apply the contract

A file handle's lines iterator reads records without their line terminators. An empty string represents an empty line and is truthy in Lua; nil ends iteration. A final unterminated line is still a line. Do not filter with a nonempty-string condition unless the contract explicitly asks to discard blank records. Preserve spaces in each record and leave the original file unchanged.

A line is a record
start / 3
A line is a record
Lua
local function example()
  local file <close> = assert(io.tmpfile())
  assert(file:write("first\n\nlast"))
  assert(file:seek("set",0))
  for line in file:lines() do io.write("[",line,"]\n") end
end
example()

Reason about the edge cases

A to-be-closed local, written local file <close> = ..., closes its resource when its scope exits, including ordinary returns and error unwinding. This is available in the standard Lua version used here; it is not a LuaJIT portability promise. Explicit close with correct error-path handling can also satisfy the lab. Garbage collection is not a deterministic substitute for the requirement that the handle be closed before return. The driver observes the actual handle after the call, so returning the right lines while leaving an open resource still fails. Use io.tmpfile in the runnable example to practice lifetime behavior without relying on host files.

Deterministic scope cleanup
start / 3
Deterministic scope cleanup

Practice

Implement M.read_lines(path). On a successful open, return a dense sequence of lines with line terminators removed, preserving empty lines and spaces; a final unterminated line is still included. Close the handle before returning and leave file contents unchanged. If opening fails return nil and open failed. Use Lua 5.5 standard I/O; explicit close or a to-be-closed local are both valid.

Run sample starts again from the first public fixture and grants no mastery evidence. Submit checks all fixtures, including edge cases. Read the specification before editing; the bug and fill phases share the build’s contract. Flashcards reinforce recall on a separate schedule.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

The first part introduces Lua through its language designer. The authored lesson and Lua 5.5 manual cover the exact APIs and advanced details in this lab.