Before this lesson, complete lua-modules-errors.
This course runs standard Lua 5.5 with its standard library. Game engines, LuaJIT and embedded host APIs are outside this sequence.
Metatables customize selected operations on tables and other values. For a table, __index handles a missing-key lookup and __newindex handles an assignment to a key absent from the table itself. They are fallback mechanisms, not hooks that intercept every raw access. If you attach __newindex directly to a populated table, replacing an existing field bypasses it.
Apply the contract
A conventional read-only view therefore uses an empty proxy. Its __index reads from a separate backing table, and its __newindex raises an error. Both replacing an existing backing key and inserting a new key target an absent proxy entry, so ordinary assignment reaches the rejection hook. Preserve false and zero by returning the backing value directly; a truthiness test would erase valid data. The lab asks for a live view, so later backing changes should be visible.
local backing={score=0,enabled=false}
local proxy=setmetatable({}, {__index=backing})
print(proxy.score,proxy.enabled)
backing.score=4
print(proxy.score)Reason about the edge cases
Add __pairs so ordinary pairs iteration delegates to the backing table, without promising iteration order. Protect the metatable through __metatable=false if the API should hide its ordinary metatable access. This is an application convention, not a security boundary: rawset or debug facilities can bypass ordinary metamethod behavior, and nested tables are not recursively frozen. The fixtures deliberately stay within the documented ordinary-lookup and assignment contract. Test a missing key, a false value, replacement of an existing key, insertion of a new key and a later backing update.
Practice
Implement M.readonly(data) as an empty proxy backed by data. Ordinary indexing reflects current backing values, including false and zero. Normal assignment to either an existing or a new key must raise an error. pairs(proxy) enumerates backing entries, and getmetatable(proxy) returns false. The proxy is not a security boundary: rawset and debug facilities are outside this contract.
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.