Lua table 用作静态配置是常见的使用情境,而用作静态配置的 Lua table 往往都有保持只读的需求,本文简单介绍了一些让 Lua table 变更为只读的知识 (代码基于 Lua 5.4)
基础
基础变更 Lua table 为只读的方法,在 《Programming in Lua》 中就已经给出了(这里),基本思路即是通过 __index 和 __newindex 两个元方法来做 table 的读写限制,代码大体如下:
代码语言:javascript复制function readonly(t)
local proxy = {}
local mt =
{
__index = t,
__newindex = function()
error("attempt to update a readonly table", 2)
end
}
setmetatable(proxy, mt)
return proxy
end
简单测试一下:
代码语言:javascript复制local r_t = readonly({ 1, 2, 3 })
print(r_t[1])
-- error here : attempt to update a readonly table
r_t[1] = 2
完善
上述的示例代码中,虽然我们已经让 table 变为了只读,但是获取 table 长度(#)或者使用 pairs 遍历 table 时都不能得到正确结果(使用 ipairs 可以得到正确结果):
代码语言:javascript复制local r_t = readonly({ 1, 2, 3 })
-- correct
for k, v in ipairs(r_t) do
print(tostring(k) .. " = " .. tostring(v))
end
-- error
print(#r_t)
-- error
for k, v in pairs(r_t) do
print(tostring(k) .. " = " .. tostring(v))
end
完善的方法也很简单,添加相应的 __len 和 __pairs 元方法即可:
代码语言:javascript复制function readonly(t)
local proxy = {}
local mt =
{
__index = t,
__newindex = function()
error("attempt to update a readonly table", 2)
end,
__len = function()
return #t
end,
__pairs = function()
return next, t, nil
end
}
setmetatable(proxy, mt)
return proxy
end
进阶
上面的示例代码中仍然存在一个比较大的问题:如果 table 中存在另外的 table 元素,经过上述 readonly 函数处理之后,这些 table 子元素仍然不是只读的:
代码语言:javascript复制local r_t = readonly({ 1, 2, 3, {} })
r_t[1] = 1 -- error
r_t[4] = {} -- error
r_t[4][1] = 1 -- correct ...
为了解决这个问题,我们需要递归的对 table 做 readonly 操作,相关代码如下:
代码语言:javascript复制local proxies = {}
function readonly(t)
if type(t) == "table" then
local proxy = proxies[t]
if not proxy then
proxy = {}
local mt =
{
__index = function(_, k)
return readonly(t[k])
end,
__newindex = function()
error("attempt to update a readonly table", 2)
end,
__len = function()
return #t
end,
__pairs = function()
local function readonly_next(t, i)
local n_i, n_v = next(t, i)
return n_i, readonly(n_v)
end
return readonly_next, t, nil
end
}
setmetatable(proxy, mt)
proxies[t] = proxy
end
return proxy
else
return t
end
end
示例代码并没有对 table 进行全量的只读变更(我们自然也可以这么做),而是在访问 table 元素时以增量方式进行的,这有益于分摊程序消耗.
问题
经过了上面几步, readonly 函数已经几近完善,但仍然存在问题,如果我们使用 rawset(类似的还有 rawget) 绕过元方法来设置 table,那么 table 仍然会被更新(而不能做到只读):
代码语言:javascript复制local r_t = readonly({ 1, 2, 3, {} })
rawset(r_t, 1, 2) -- correct ...
如果需要解决这个问题,目前就需要在宿主语言侧(譬如 C)来实现只读的 table 类型,并导出给 Lua 来使用.
参考资料
- Programming in Lua
- Read Only Tables
- Generalized Pairs And Ipairs
- How can I implement a read-only table in lua?