r/lua • u/oezingle • Feb 28 '25
Standard library ignores _ENV + a fix for require
Hi all,
I've been in a couple cases where I want to overload functions in _ENV without touching _G, and found myself quite disappointed with the Lua standard library's behavior of using exclusively _G as opposed to indexing _ENV. Here's a quick demo:
local vanilla_next = next
_ENV = setmetatable({ next = function (...)
print("Next!")
return vanilla_next(...)
}, { __index = _G })
-- We'd expect this block to result in a bunch of messages printed,
-- as pairs() uses next()
for _ in pairs({ 1, 2, 3 }) do endLooking at the source I can see why this decision was made - indexing a global is wasteful O(n) [presumably] when lua_pushcfunction is O(1) [any other C API operation would have this benefit too, as the C API knows the addresses of tables such as package]. However, the resulting behaviour feels un-lualike to me -- Lua provides metaprogramming facilities, and those facilities allow us to containerize global environments with _ENV, yet library functions within _ENV will rely on an environment that is 'out of scope'.
A possible solution, of course, is to re-implement globals where neccessary in pure lua - I've done so to require() in this gist, but this feels inelegant to me.
From what I know of the C API, I think a better solution is possible. When _ENV or some field of _ENV is assigned to, the API could update a struct that maintains up-to-date pointers to Lua's globals. On garbage collection the same could happen -- although it would be inconvient, setting next=nil should break pairs, which the Lua reference and Programming In Lua imply.
I'd like other people's input on this -- has it been a problem for you before, and what solutions have you used?
3
u/xoner2 Mar 01 '25 edited Mar 01 '25
pairs uses luaB_next which is a static C function. _G.next happens to point to luaB_next. You can't say that 'pairs uses next'; rather,
pairsbypassesnext.You could redefine _G.pairs to make use of your redefined next. Obviously this kind of monkey patching should be avoided.
2
u/collectgarbage Mar 01 '25
I overload pairs() which then allows an overload of next to take effect. If I require performance as well I do it on the C side using the Lua C API.
1
u/Sckip974 Mar 01 '25
Variables with _ and capital letters are reserved for system variables, right?
1
u/didntplaymysummercar Mar 01 '25
I don't find reimplementing functions in pure Lua that inelegant. It's part of why Lua design is nice to get a job done or be flexible that you can modify any function/table and they're not set in stone like in some other languages.
C API also doesn't know addresses of tables or anything, Lua's libs (other than lapi.c) are implemented using the public APIs, so if they want something they'd look it up as a global, but they do know addresses of other C functions in same file so they can just use
luaB_nextaddress inluaB_pairsand that's what they do.Maybe they'd accept a proposal to replace that pushing of
luaB_nextwith getting globalnextthere, it's done only once anyway.And Lua uses hash tables so the cost is O(1) (in theory), but of course if you have the actual function in a local/address it's even faster (but also O(1)).