The big difference between Lua and Python C APIs is that Python values are exposed directly as first-class values (as pointers in CPython, and as handles in HPy). If one native function wants to call another native function and pass it a Python object, it can do so directly - the VM is none the wiser for it, except for reference count updates.
In Lua API, all Lua values are bound to a stack, and C functions have to exchange values via that stack - there's no free-standing "Lua value" type in the API. Thus, the runtime is fully aware of all the objects that flow back and forth, even if one native Lua function is calling another native Lua function.
> In Lua API, all Lua values are bound to a stack, and C functions have to exchange values via that stack - there's no free-standing "Lua value" type in the API.
That's not 100% correct. You can have values unbound from the stack, they're just typed, rather than having one "value" type. (lua_Number, lua_CFunction, etc). Though functions bound from do still use the stack for taking/returning values. (However your C functions can directly work on other value types).
Correct me if I'm wrong, but doesn't this only work for value types? i.e. there's no way you can hold a reference to a Lua (not C) function, or a table, without the stack.
If what you want is a reference to any Lua object, that you can grab from somewhere random in C, that doesn't exist on the stack, then you'll be using Lua's registry.
Yep, that's exactly what I meant. It goes back to the same thing - all Lua objects exist strictly in the "Lua world" (be it the stack or the registry), and anything that wants to exchange them has to go through those mechanisms. In Python, you just get a PyObject*, and you can pass it around as much as you want - the only thing you have to take care of is to reference-count it properly.
A registry object in Lua is an integer in C, and you can pass it around as much as you want. However, you have to be careful, because it can be eliminated by the GC, in much the same way as a PyObject pointer.
The registry object really is just an address. It's the equivalent of: lua_State->top+LUA_REGISTRYINDEX[reference] in C.
There's no functional difference to a PyObject pointer.
In Lua API, all Lua values are bound to a stack, and C functions have to exchange values via that stack - there's no free-standing "Lua value" type in the API. Thus, the runtime is fully aware of all the objects that flow back and forth, even if one native Lua function is calling another native Lua function.