Lua - Controlling Access to a Table



Lua metatable has two specific fields __index and __newindex to control table access. In this chapter, we're covering both the fields with metamethods with examples.

Using __index metamethod

__index metamethod is activated or invoked by Lua when we try to access a key in a table which is not present. We can use __index metamethod for −

  • Default Values − We can return a different default value instead of returning nil when a key is missing in the target table.

  • Delegation − We can forward the key lookup to other table.

  • Lazy Loading − We can load values when first accessed instead of load time to improve performance.

  • Virtual Property − We can compute values dynamically based on provided key instead of looking up stored data.

Example - Setting default values using __index

main.lua

-- Default values
local defaults = { width = 100, height = 50 }

-- main table
local shape = {}

-- metatable
local meta = { __index = defaults }

-- set metatable
setmetatable(shape, meta)

-- get default values of keys
-- prints 100
print(shape.width) 
-- prints 50
print(shape.height)
-- prints nil, as not present in defaults
print(shape.color)

Output

When we run the above program, we will get the following output−

100
50
nil

Example - Delegation using __index

main.lua

-- main table
local shape = { width = 100, height = 50 }

-- proxy table
local proxy = {}

-- metatable to delegate to main table
local meta = { __index = shape }

-- set metatable to proxy
setmetatable(proxy, meta)

-- get value from main table via proxy
print(proxy.width) 

-- set a value to proxy, but not to main table
proxy.color = "red"

-- try to get color from shape, prints nil
print(shape.color)

Output

When we run the above program, we will get the following output−

100
nil

Using __newindex metamethod

__newindex metamethod is activated or invoked by Lua when we try to assign a new key to the table. We can use __newindex metamethod for −

  • Read Only Table − We can make a table readonly by not allowing any new entry to it.

  • Validation − We can perform validation on the value assigned as required type of value or range of values.

  • Side Effects − We can perform actions as side effects while setting values like logging, tracing etc.

  • Delegation − We can even assign value to different table or object.

Example - Delegation using __newindex

main.lua

-- main table
local config = { max_users = 10 }

-- set metatable to make table readonly
-- no new entry is allowed
setmetatable(config, { __newindex = function(table, key, value)
  error("No new entry allowed.", 2)
end })

-- this will throw an error
config.max_connections = 15 

Output

When we run the above program, we will get the following output−

lua: main.lua:11: No new entry allowed.
stack traceback:
    [C]: in function 'error'
    main.lua:7: in metamethod 'newindex'
    main.lua:11: in main chunk
    [C]: in ?

Example - Validation using __newindex

main.lua

-- main table
local settings = {}

-- set a metatable to validate inputs to be numbers only
setmetatable(settings, { __newindex = function(table, key, value)
  if type(value) == "number" then
    rawset(table, key, value) 
  else
    error("Value for " .. key .. " must be a number", 2)
  end
end })
-- set a valid value
settings.volume = 75
print(settings.volume)
-- setting string throws an error
settings.base = "high" 

Output

When we run the above program, we will get the following output−

75
lua: main.lua:16: Value for base must be a number
stack traceback:
    [C]: in function 'error'
    main.lua:9: in metamethod 'newindex'
    main.lua:16: in main chunk
    [C]: in ?
Advertisements