Read only iterable table in lua?
Asked Answered
G

2

5

I want to have a read only table in my Lua program. If ever a key is removed or a key is associated with a new value, an error must be thrown.

function readonly(table)
    local meta = { } -- metatable for proxy
    local proxy = { } -- this table is always empty

    meta.__index = table -- refer to table for lookups
    meta.__newindex = function(t, key, value)
        error("You cannot make any changes to this table!")
    end

    setmetatable(proxy, meta)
    return proxy -- user will use proxy instead
end

It works great.

t = { }
t["Apple"] = "Red"
t[true] = "True!"
t[51] = 29

for k,v in pairs(t) do
    print(v)
end

t = readonly(t)
t[51] = 30

Prints

Red
True!
29
input:7: You cannot make any changes to this table!

Problem

for k, v in pairs(t) do
   print(v)
end

Will print nothing under all circumstances now. That's because the proxy table will never have anything inside of it. pairs apparently never calls index and thus cannot retrieve anything from the actual table.

What can I do to make this readonly table iterable?

I'm on Lua 5.1 and have access to these metamethods:

Lua 5.1 Manual

Glossy answered 23/12, 2017 at 23:43 Comment(0)
R
5

You can modify standard Lua function pairs to work correctly with your read-only tables.

local function readonly_newindex(t, key, value)
   error("You cannot make any changes to this table!")
end

function readonly(tbl)
   return
      setmetatable({}, {
         __index = tbl,
         __newindex = readonly_newindex
      })
end

local original_pairs = pairs

function pairs(tbl)
   if next(tbl) == nil then
      local mt = getmetatable(tbl)
      if mt and mt.__newindex == readonly_newindex then
         tbl = mt.__index
      end
   end
   return original_pairs(tbl)
end

Usage:

t = { }
t["Apple"] = "Red"
t[true] = "True!"
t[51] = 29

for k,v in pairs(t) do
   print(k, v)
end

t = readonly(t)

for k,v in pairs(t) do
   print(k, v)
end

t[51] = 30
Ronnaronnholm answered 24/12, 2017 at 0:37 Comment(0)
P
1

One solution is to create a wholly custom iterator for the table.

function readonly(table)
    local meta = { } -- metatable for proxy
    local proxy = { } -- this table is always empty

    meta.__index = table -- refer to table for lookups
    meta.__newindex = function(t, key, value)
        error("You cannot make any changes to this table!")
    end

    local function iter()
        return next, table
    end

    setmetatable(proxy, meta)
    return proxy, iter -- user will use proxy instead
end

Usage:

t = { }
t["Apple"] = "Red"
t[true] = "True!"
t[51] = 29

for k,v in pairs(t) do
    print(v)
end

t, tIter = readonly(t)
t[51] = 30

for k, v in tIter do
   print(v)
end
Presence answered 24/12, 2017 at 0:58 Comment(2)
Lua 5.2+ has __pairs/__ipairs metamethods, so it's not necessary to explicitly return/use custom iterator . pairs()/ipairs() can use that custom iterator implicitly.Giantism
@Giantism True and that was my initial thought, but the OP is using 5.1 so that solution is not available.Presence

© 2022 - 2024 — McMap. All rights reserved.