
Da mesma forma que o Neovim rompeu com o legado do Vim ao adotar Lua, eu queria desacoplar configuração e lógica do C++ compilado em um novo ambiente: o Hyprland. Mas este post não é sobre o Hyprland — é sobre como dar ao seu projeto C++ uma camada de scripting em tempo de execução sem inchá-lo ou entregar sua alma ao Python.
Você quer expor lógica interna. Você quer extensibilidade. Você quer Lua.
Por que Lua?
- Porque é leve.
- Porque não vem com pilhas incluídas — em vez disso, te dá fósforos.
- Porque embutir Python é uma bagunça, e escrever parsers de config para seu app C++ é entediante pra caramba.
Lua foi projetada para ser embutida. Sua API C é de baixo nível, mas incrivelmente simples. Você mantém controle total enquanto seus usuários ganham uma sandbox para fazer o que quiserem.
Passo 1: Configurar o Runtime Lua
Você precisa dos headers e da biblioteca Lua. No Arch ou Fedora:
sudo pacman -S lua # Arch
sudo dnf install lua-devel # Fedora
Agora comece com uma integração mínima:
#include <lua.hpp>
int main() {
lua_State* L = luaL_newstate(); // cria um estado Lua
luaL_openlibs(L); // carrega as libs padrão do Lua
luaL_dofile(L, "example.lua"); // executa um script
lua_close(L); // limpa
return 0;
}
É tudo que você precisa para iniciar o Lua a partir do C++. Agora você tem um cérebro programável dentro do seu app C++.
Passo 2: Chamar Funções Lua a Partir do C++
Você quer que seu script Lua defina funções que você pode chamar do C++. Digamos que seu example.lua tenha:
function add(a, b)
return a + b
end
Do C++:
lua_getglobal(L, "add");
lua_pushnumber(L, 2);
lua_pushnumber(L, 3);
lua_pcall(L, 2, 1, 0);
double result = lua_tonumber(L, -1);
lua_pop(L, 1);
Pronto. O Lua fez a conta e você ainda é dono da stack.
Passo 3: Chamar C++ a Partir do Lua
É aqui que fica poderoso. Você escreve funções C++ que o Lua pode chamar.
int cpp_say_hello(lua_State* L) {
printf("Hello from C++!\n");
return 0;
}
// Registra a função:
lua_register(L, "say_hello", cpp_say_hello);
Agora o Lua pode:
say_hello()
Você acabou de estender o Lua com código nativo.
Passo 4: Escrever um Módulo C++ para Lua
Em vez de poluir o namespace global do Lua, é mais limpo definir um módulo completo e registrar as funções dentro de uma tabela.
Aqui está um módulo básico que imita o que lfs.currentdir() faz: chama getcwd() do C e retorna o diretório de trabalho atual para o Lua.
Módulo: fs.cpp
#include <lua.hpp>
#include <unistd.h> // para getcwd
#include <limits.h> // para PATH_MAX
int lua_get_cwd(lua_State* L) {
char buffer[PATH_MAX];
if (getcwd(buffer, sizeof(buffer)) != NULL) {
lua_pushstring(L, buffer);
return 1;
} else {
return luaL_error(L, "failed to get current directory");
}
}
static const luaL_Reg fslib[] = {
{"cwd", lua_get_cwd},
{NULL, NULL}
};
extern "C" int luaopen_fs(lua_State* L) {
luaL_newlib(L, fslib);
return 1;
}
O que está acontecendo:
- Usamos
getcwd()para recuperar o diretório atual. - Se funcionar, empurramos o resultado para a stack do Lua.
- Se falhar, levantamos um erro Lua (mais limpo do que retornar nil e esperar que o usuário verifique).
luaL_newlib()cria uma tabela Lua com suas funções nativas — essa é a forma idiomática de escrever um módulo Lua em C ou C++.
Em Lua:
local fs = require("fs")
print("Diretório atual:", fs.cwd())
Compilar:
g++ -shared -fPIC -o fs.so fs.cpp -llua
Garanta que fs.so esteja no seu package.cpath ou no diretório de trabalho atual ao rodar seu script Lua.
Embutir Lua não é inseguro, mas o que você expõe é que importa.
- Use funções
luaL_check*para validar tipos de entrada. - Não exponha chamadas de sistema que você não gostaria que fossem acionadas por um script.
- Você pode fazer sandbox no Lua não carregando todas as libs ou substituindo
os.executepor um no-op. - Você pode monitorar a execução definindo limites de instrução com
lua_sethook.
Se você está dando acesso ao Lua para usuários, você se deve o trabalho de travar o que eles podem tocar.
TL;DR
Se seu projeto C++ tem qualquer tipo de comportamento dinâmico, Lua é sua melhor aposta para expor uma API limpa e scriptável sem transformar seu codebase em um cemitério de parsers de config e flags de linha de comando.
É fácil de embutir, incrivelmente rápido e battle-tested em engines de games, editores e agora… no seu próprio projeto.
Quer ver isso em ação? Escrevi o HyprLua para trazer Lua ao Hyprland. Mas você pode construir sua própria camada de runtime para qualquer projeto. Lua te dá poder sem tirar o controle.