Reading — step 1 of 4
Learn
Lua is embeddable. Its C API is famously small (~250 functions), stack-based, and the reason Lua is everywhere from Redis to ROS to game consoles.
The Lua stack:
The API exposes Lua values through a virtual stack indexed by integers. Positive indices count from the bottom (1, 2, 3...); negative indices from the top (-1, -2, -3...).
// C code calling into Lua:
lua_State *L = luaL_newstate(); // create VM
luaL_openlibs(L); // load standard library
luaL_dostring(L, "return 1 + 2"); // run code, leaves result on stack
int result = lua_tointeger(L, -1); // top of stack
printf("%d\n", result);
lua_pop(L, 1); // remove from stack
lua_close(L);
Calling Lua functions from C:
lua_getglobal(L, "math"); // push math table
lua_getfield(L, -1, "sqrt"); // push math.sqrt onto stack
lua_pushnumber(L, 16.0); // push argument
lua_call(L, 1, 1); // 1 arg, 1 return
double r = lua_tonumber(L, -1);
Exposing C functions to Lua:
static int myAdd(lua_State *L) {
double a = luaL_checknumber(L, 1);
double b = luaL_checknumber(L, 2);
lua_pushnumber(L, a + b);
return 1; // number of return values
}
// Register:
lua_pushcfunction(L, myAdd);
lua_setglobal(L, "myAdd");
In Lua: print(myAdd(3, 4)) → 7.
Mental model:
- Lua side: cooperative, no preemption.
- C side: full control. Long C functions block Lua entirely.
- Communication is data on the stack — push args, call, read results.
- C errors must use
luaL_error(which longjmps); neverreturn -1or set errno.
Userdata for opaque C state:
// Allocate a struct on Lua's GC heap:
MyStruct *p = (MyStruct *)lua_newuserdata(L, sizeof(MyStruct));
p->field = 42;
// Attach a metatable for method dispatch / __gc finalizer.
luaL_setmetatable(L, "MyStructMT");
Real embedding examples:
- Redis:
lua_Stateper connection, sandboxed env, restricted libs - Roblox/Luau: forked Lua, custom typed extension
- Neovim: Lua VM bound to editor APIs
- Wireshark dissectors, nginx/OpenResty modules, Premake build scripts
LuaJIT FFI (Lua-side, no C needed):
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("hello from FFI\n")
LuaJIT's FFI lets you declare C signatures in Lua and call them directly — way faster than wrapping each in a C binding.
We can't run C in this sandbox; this lesson is conceptual. Practice the mental model: stack discipline, push/pop, return-count.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…