Read Only Tables |
|
|
By: KevinBaca |
|
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. |
|
Lua Version: 5.x Prerequesites: Familiarity with metamethods (see MetamethodsTutorial) This article describes a technique for making a table readonly. It will prevent the inadvertant modification of any member of a readonly table. Note, however, that members of members of a readonly table can still be modified unless they too are made readonly (see RecursiveReadOnlyTables for an alternative implementation handling recursion). Suppose we have a table of values that we would like to remain constant. |
|
We can define read-only (constant) tables like this: |
|
Directions = { LEFT = 1, RIGHT = 2, UP = 3, DOWN = 4 |
|
Directions = readonlytable { LEFT = 1, RIGHT = 2, UP = 3, DOWN = 4, otherstuff = {} |
We can use the metamathods __index and __newindex to ensure that these values never change. |
if we define the readonlytable helper functions as follows: |
|
mt = { __index = Directions, __newindex = function( table, key, value ) error( "Attempt to modify read-only table" ) end } Directions = setmetatable( {}, mt ) |
|
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. |
|
Note that, as mentioned above, members of members of readonly tables can still be modified. For example: |
|
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 = { LEFT = 1, RIGHT = 2, UP = 3, DOWN = 4, otherstuff = { } } |
|
> Directions.otherstuff = nil -- will fail Attempt to modify read-only table > Directions.otherstuff.foo = 1 -- allowed |
The table Directions.otherstuff cannot be changed or removed, but its contents can be changed or removed: |
Also, rawset() and table.insert can still be used to directly modify a read-only table: |
|
> Directions.otherstuff = nil -- will fail Attempt to modify read-only table > Directions.otherstuff.foo = 1 -- OK |
|
rawset(Directions, "LEFT", 5) print(Directions.LEFT) -- prints 5 table.insert(Directions, 6) print(Directions[1]) -- prints 6 |
Note also that rawset() can still be used to modify a readonly table, and that access to setmetatable() will allow the user to change this behaviour. Furthermore table.insert, etc. will also directly modify the table. |
|
If you really need to avoid that, you could implement the read-only table in C. |
Note, however, that this construct interferes with pairs, ipairs, next, the # operator, and other forms of table iteration. For example, |
Furthermore, this method of creating read-only tables interferes with pairs, ipairs, next, the # operator, and other forms of table iteration. For example, |
|
-- prints nil! print(next(Directions)) -- prints "0"! print(#Directions) |
|
print(next(Directions)) -- prints nil! print(#Directions) -- prints "0"! |
|
See also GeneralizedPairsAndIpairs for ways to handle that. |
|
See also GeneralizedPairsAndIpairs for ways to handle that. Original author: KevinBaca === See Also === * RecursiveReadOnlyTables - an alternative implementation handling recursion. |
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