【语法算法】
rawset 的安全写入本质就这一行:
rawset(t, key, value)
rawset 绕过 __newindex 元方法直接写入表——rawset 的全部语义就这一行。可这一行的前后各有一个坑:前面是"表设了 __newindex 后普通赋值不走 rawset",后面是"rawset 里调 rawset 会死循环"。今天拆开。
一、拦截、绕过、安全三段
rawset 的三段。拦截:表的元表设了 __newindex 后所有新键赋值都走 __newindex 函数——普通赋值被拦截——rawset 绕过拦截直接写入——绕过的是元方法不是表本身。绕过:rawset(t, k, v) 不查元表直接写——即使元表有 __newindex 也不触发——绕过后写入的值不会经过 __newindex 的转换逻辑——rawset 的语义就是"无视拦截直接写入"。安全:rawset 是 C 级函数——没有函数调用开销——比普通赋值还快——但 rawset 不做类型检查——写入什么类型就是什么类型——调用方负责保证类型正确。三段连起来:rawset 玩的是"绕过抽象层直接写底层"——调试和性能分析时 rawset 比 # 更诚实。
graph TD
A["t.newKey = value"] --> B{"t有__newindex?"}
B -->|是| C["调用__newindex(t, k, v)"]
C --> D["拦截或转换"]
B -->|否| E["直接写入"]
A --> F["rawset(t, k, v)"]
F --> G["绕过__newindex直接写入"]
二、rawset 的正确写法
local proxy = setmetatable({}, {
__newindex = function(t, k, v)
if type(v) == "string" then
rawset(t, k, v:upper()) -- 绕过__newindex写入转换后的值
else
SL:ShowError("只接受字符串")
end
end
})
proxy.name = "hello"
print(proxy.name) -- HELLO
proxy.age = 42
-- 报错: 只接受字符串
rawset 在 __newindex 函数里写入转换后的值——绕过元方法避免无限递归——如果用普通赋值就会再次触发 __newindex——无限递归直到栈溢出。
三、rawset 的纪律与 rawget 的配对
rawset 的纪律是"不在 __newindex 里调 rawset 写入未转换的值"——__newindex 的职责是拦截和转换——转换完成后用 rawset 写入——如果 rawset 写入的值还会触发 __newindex,就形成了无限循环。和 rawget 的配对也值得记:rawget 绕过 __index 直读、rawset 绕过 __newindex 直写——raw 家族的读写操作对称配对——调试时用 raw 家族逐层排查,就能定位问题出在哪一层元方法。最后一条纪律:rawset 不触发 __newindex 但也不触发 __close——to-be-closed 变量用 rawset 赋值不会触发清理——to-be-closed 变量的赋值必须走普通赋值路径。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 var ld2=Math.max(1,Math.round(d 0.25)) ——先拆这一行。连枝转移的账就在…
【游戏功能】 前阵子的投诉出得冤:玩家开着让先纹被怪的头一招打掉了半管血,找到客服问"让先让了个寂寞"。翻流水发现机制没算错…
【游戏功能】 上一版留了条只在连打两场时冒头的隐蔽报错:秋后纹第二次开的清算比第一次翻倍——头一场十二笔账清完没清零,第二场…
【游戏功能】 先抛一个坑:砍出去的刀,能收回来吗?刀光落了地、数字蹦出来、血条掉了——按理说木已成舟。可玩家心里都有过那一拍…
【游戏功能】 var fresh=(tg===M)?fr.a:fr.b ——先拆这一行。两只怪,两本"点没点过卯"的小账,出…
【游戏功能】 上个月一场差评事故:一个闪避堆得高的怪成了玩家的噩梦——十刀落空七刀,打得着的两三刀又不痛不痒,玩家在频道里骂…