__index and __newindex
Prerequisites: Metatables and metamethods
__index runs when you read a key that isn't in the table;
__newindex when you assign to a key that isn't there.
Default values
local defaults = {color = "black", size = 12}
local settings = setmetatable({size = 16}, {__index = defaults})
print(settings.size)
print(settings.color)
print(rawget(settings, "color"))
16
black
nil
When __index is a table, Lua looks the key up there. rawget
reads the table directly, skipping metamethods.
Computed values
local squares = setmetatable({}, {
__index = function(t, n)
local value = n * n
rawset(t, n, value) -- cache it
return value
end,
})
print(squares[12])
print(rawget(squares, 12))
144
144
When __index is a function, it's called with the table and the key.
Read-only tables
local function readOnly(t)
return setmetatable({}, {
__index = t,
__newindex = function(_, key)
error("attempt to modify read-only field '" .. tostring(key) .. "'", 2)
end,
})
end
local Colors = readOnly({RED = "#f00", GREEN = "#0f0"})
print(Colors.RED)
local ok, err = pcall(function() Colors.BLUE = "#00f" end)
print(ok, err)
#f00
false main.lua:12: attempt to modify read-only field 'BLUE'
The proxy table is empty, so every write goes through __newindex.
Catching typos in globals
setmetatable(_G, {
__newindex = function(_, name) error("assignment to undeclared global '" .. name .. "'", 2) end,
__index = function(_, name) error("undeclared global '" .. name .. "'", 2) end,
})
print(pcall(function() return undefinedVariable end))
print(pcall(function() counter = 1 end))
false main.lua:6: undeclared global 'undefinedVariable'
false main.lua:7: assignment to undeclared global 'counter'
_G is the table that holds all globals. This "strict mode" trick turns
accidental globals into errors at runtime.