Read Only Tables

lua-users home
wiki

This article describes a technique for making a table read-only via metamethods in Lua 5.0/5.1 (for background on metamethods see the MetamethodsTutorial). The approach will only prevent the inadvertent modification of any member of a read-only table.

We can define read-only (constant) tables like this:

Directions = readonlytable {
  LEFT   = 1,
  RIGHT  = 2,
  UP     = 3,
  DOWN   = 4,
  otherstuff = {}
}

if we define the readonlytable helper functions as follows:

function readonlytable(table)
   return setmetatable({}, {
     __index = table,
     __newindex = function(table, key, value)
                    error("Attempt to modify read-only table")
                  end,
     __metatable = false
   });
end

Note that readonlytable does not return the table originally passed to it but rather a proxy table. The proxy table is given a metatable with the metamethods __index and __newindex to ensure that the proxy table values never change. Setting the __metatable metamethod prevents tampering with the metatable itself--the client cannot obtain nor change the metatable via getmetatable and setmetatable functions.

Now if we try to modify any member of Directions we will get an error.

> Directions.LEFT = 33
Attempt to modify read-only table

Although members of the read-only table cannot be changed, it is still possible to modify members of members of read-only tables (unless they too are explicitly made read-only tables):

> Directions.otherstuff = nil    -- will fail
Attempt to modify read-only table
> Directions.otherstuff.foo = 1  -- allowed

Also, rawset() and table.insert can still be used to directly modify a read-only table:

rawset(Directions, "LEFT", 5)
print(Directions.LEFT)         -- prints 5
table.insert(Directions, 6)
print(Directions[1])           -- prints 6

If you really need to avoid that, you could implement the read-only table in C.

Furthermore, this method of creating read-only tables interferes with pairs, ipairs, next, the # operator, and other forms of table iteration. For example,

-- prints nothing!
for k,v in pairs(Directions) do
  print(k,v)
end

print(next(Directions))  -- prints nil!
print(#Directions)       -- prints "0"!

See also GeneralizedPairsAndIpairs for ways to handle that.

Original author: KevinBaca

See Also


RecentChanges · preferences
edit · history
Last edited March 8, 2016 3:26 pm GMT (diff)