SolVM: Lua Turbinado com Go

Quem mexe com Lua já sabe: a linguagem é uma beleza. Leve, rápida, encaixa que nem uma luva em games, no Nginx com OpenResty, no Redis... uma maravilha pra scripting e pra dar aquela inteligência extra onde precisa. Mas aí, quando o projeto começa a pedir mais, quando a gente precisa de um backend mais parrudo, lidar com um monte de requisição ao mesmo tempo, ou simplesmente ter umas ferramentas na mão pra criptografia, pra ler uns JSON, uns YAML da vida, a gente sente falta de um "kit de sobrevivência" mais completo. A gente acaba caçando lib pra todo lado, se embrenhando em binding C, e dando uns pulos pra fazer o Lua se esticar.

Foi dessa dor, dessa vontade de ter um Lua mais "pronto pra briga" no mundo moderno, que nasceu o SolVM. Pensem nele como um runtime pra Lua, só que com um motorzão Go por baixo do capô. A ideia não é mudar o Lua que a gente ama, mas dar a ele um canivete suíço cheio de ferramentas que hoje em dia são cruciais.

Por que raios fuçar nisso? Pra que mais um runtime?

A real é que a gente queria um Lua que não fizesse a gente suar frio pra fazer coisas que em outras plataformas são mais diretas.

Tipo, imagina você precisar de concorrência de verdade. Como é que você faz um servidor Lua aguentar um monte de gente conectada ao mesmo tempo, sem que uma requisição demorada trave todo o resto? Ou como orquestrar várias tarefas que precisam rodar em paralelo e trocar informações entre si de forma segura?

-- Disparando uma tarefa em paralelo
go(function()
    local dados = faz_algo_demorado_na_rede()
    send("meu_canal_de_dados", dados)
end)

-- Em outro lugar, esperando por esses dados
local resultado = receive("meu_canal_de_dados")
print("Recebi:", resultado)

E networking? Não só um clientezinho HTTP básico. E se você precisa de um servidor TCP pra um protocolo customizado, ou um servidor UDP pra telemetria rápida? Ou até um servidor HTTP que também fale WebSockets pra um chat em tempo real, já com TLS pra segurança? No SolVM, a gente tentou trazer isso pra perto.

-- Subindo um servidor HTTP rapidinho
create_server("meu_app", 8080, false) -- nome, porta, usa_tls?

handle_http("meu_app", "/api/users", function(req)
    -- req.method, req.path, req.query, req.headers, req.body
    local users = { {id=1, nome="Fulano"}, {id=2, nome="Ciclana"} }
    return { status = 200, body = json_encode(users), headers = {["Content-Type"]="application/json"} }
end)

start_server("meu_app")

Criptografia é outro ponto. Fazer um AES direitinho, com IV e tudo, ou gerar um par de chaves RSA pra trocar segredos, não deveria ser um bicho de sete cabeças em Lua.

local chave_secreta = crypto.random_bytes(32) -- Chave AES-256
local iv_aleatorio = crypto.random_bytes(16)    -- IV para AES
local dados_cifrados = crypto.aes_encrypt("meu segredo", chave_secreta, iv_aleatorio)

E quem nunca precisou ler um arquivo de configuração em TOML, receber um JSON de uma API, gerar um relatório em CSV, ou até desenterrar um .ini antigo? Ter isso na mão, sem precisar instalar uma penca de libs externas, economiza um tempo danado.

O SolVM é basicamente isso: um esforço pra trazer essas "baterias inclusas" pro mundo Lua, mas de um jeito que faça sentido, que seja fácil de usar e que não pese uma tonelada.

Como a Mágica (ou melhor, o Go) Acontece por Baixo dos Panos

Primeiro, o SolVM não é uma VM Lua nova. Ele usa o gopher-lua, que é uma implementação de Lua feita em Go. O SolVM é o runtime, o ambiente que roda seus scripts Lua e, o mais importante, que dá acesso a um monte de módulos nativos escritos em Go. É tipo o Lua pegando uma carona num carro potente e bem equipado.

A parte da concorrência é um bom exemplo. Quando você faz go(minha_funcao_lua), o SolVM cria uma goroutine lá no Go pra rodar sua função Lua. Cada goroutine dessas tem seu próprio "mundo Lua" isolado, o que já ajuda a evitar um monte de dor de cabeça com variável global sendo alterada por tudo quanto é lado ao mesmo tempo. Pra elas conversarem, a gente usa chan("nome_do_canal", buffer). Se o buffer é zero (ou não põe nada), o send espera o receive e vice-versa, tudo sincronizadinho. Se tem buffer, o send só espera se o canal estiver cheio. É uma forma bem direta de coordenar tarefas que rodam em paralelo. E se você precisa que uma tarefa fique de olho em vários canais ao mesmo tempo, tipo "quem responder primeiro, eu pego", tem o select("canal1", "canal2") que faz exatamente isso. Pra não deixar o programa principal morrer antes da hora, o wait() segura as pontas até todas as goroutines filhas terminarem. Isso muda completamente o jogo pra fazer um servidor Lua que aguente o tranco ou pra processar dados em background sem travar nada.

Na parte de networking, a ideia foi dar um canivete suíço pro Lua. Pra montar um servidor web, como mostrei antes, é coisa de poucas linhas. Você cria o servidor, define as rotas com handle_http (passando uma função Lua que recebe a requisição e devolve a resposta) e manda iniciar. Se precisar de WebSockets, handle_ws faz o truque, e sua função Lua recebe um objeto pra mandar e receber mensagens do cliente. Precisa fazer chamadas pra outras APIs? http_get(url) e http_post(url, corpo, tipo) resolvem. E se for algo mais customizado, http_request(metodo, url, corpo, headers) te dá controle total. E não para aí, tem TCP com tcp_listen e tcp_connect, e UDP com udp_recvfrom e udp_sendto. Até resolve_dns(dominio) pra consultar IPs tá na mão. Dá pra imaginar fazer um proxy reverso, um bot de chat, um serviço de telemetria, tudo em Lua puro, mas com a performance e a robustez do Go por trás.

Quando o assunto é manipular dados, a gente tentou cobrir o básico e um pouco mais. JSON com json_encode e json_decode é de praxe. Mas tem também TOML (toml.encode e decode), ótimo pra config; YAML (yaml.encode e decode), que é bem legível; CSV com csv.read(arquivo) e csv.write(arquivo, dados)); e até INI (ini.read e write). Suportar JSON com comentários (JSONC) com jsonc.decode também ajuda a deixar os arquivos de configuração mais amigáveis. Ter isso tudo nativo, sem precisar de um require pra cada formato, é uma mão na roda.

E tem mais um monte de ferramentas úteis embutidas. A parte de cripto é bem recheada: hashes como MD5, SHA256, SHA512; Base64; criptografia simétrica como AES (com controle de chave e IV, como no snippet lá em cima), DES e RC4; geração de par de chaves RSA com crypto.rsa_generate(bits); e até crypto.random_bytes(contagem) pra gerar aleatoriedade segura. Precisa de um ID único? uuid.v4() e uuid.is_valid(). Quer gerar uns dados aleatórios? O módulo random tem random.int(min, max) e random.string(tamanho).

Pra mexer com arquivos e pastas, tem read_file(caminho), write_file(caminho, conteudo) e list_dir(pasta) que te dá uma lista do que tem dentro com nome, tamanho, se é pasta... Bem direto ao ponto. E se precisar empacotar ou desempacotar arquivos, o módulo tar te deixa criar (tar.create), listar (tar.list) e extrair (tar.extract) arquivos TAR, e ainda com opção de comprimir com GZip junto.

-- Criando um backup compactado de uma pasta
tar.create("backup_docs.tar.gz", "./documentos", true) -- arquivo_saida, origem, comprime?

Outras coisinhas que ajudam: um módulo text pra dar um trato em strings (trim, split, join, replace, starts_with...); um datetime pra lidar com datas e horas (datetime.now(), datetime.format(timestamp, "layout Go"), datetime.add(timestamp, "2h30m")...); e um dotenv pra carregar variáveis de ambiente de arquivos .env (dotenv.load() e dotenv.get()), que é uma prática bem comum pra guardar configs e segredos.

Pra quem gosta de ver o código evoluir rápido, o hot reloading com watch_file(arquivo, callback) e reload_script() é uma mão na roda. Mexeu no código, salvou, ele já recarrega na hora. E pra dar uma espiada no que tá rolando por dentro, trace() mostra a pilha de chamadas, check_memory() dá um panorama da memória e das goroutines, e get_goroutines() lista quem tá rodando em paralelo.

E pra não virar uma bagunça de código, o sistema de importar módulos com import("meu_modulo") é bem flexível. Pode ser um arquivo .lua solto, uma pasta cheia de módulos, ou até um .zip com tudo dentro, que pode estar na sua máquina ou numa URL.

A Ideia por Trás do SolVM: Lua Ágil, Go Forte

O SolVM não quer ser um Node.js ou um Python com cara de Lua. A gente quer que ele seja o melhor lugar possível pra rodar Lua, aproveitando o que o Go tem de bom pra dar uma infraestrutura sólida por baixo. O resultado é um ambiente que mantém aquela delícia de programar em Lua, que é simples e rápido, mas com a força e a confiança de um sistema que tem Go no seu núcleo. Você escreve Lua, mas por baixo tem a performance e a estabilidade do Go trabalhando pra você.

É um esforço pra fazer o Lua ser uma opção ainda mais interessante pra um monte de coisa, desde scripts pequenos e ferramentas de linha de comando, até serviços de backend mais sérios, microsserviços, automações... A ideia é dar pro dev Lua as ferramentas pra fazer mais, com menos dor de cabeça e mais segurança.

Se você, como eu, curte o Lua pela sua simplicidade mas às vezes sente falta de um "algo a mais" pra encarar uns projetos mais cabeludos, talvez o SolVM te dê uma força. É um convite pra gente redescobrir o que dá pra fazer com Lua quando ele ganha uns poderes extras.

Obrigada pela atenção

Github do Projeto

Achei muito legal as funcionalidades que vocês adicionaram, mas tive uma dúvida dúvida! Lua roda muito próximo do C num geral, então fico pensando por que criar um novo VM em Go. Claro, a funcionalidade do go(funcao_lua) é muito bacana, mas o quão bem essa VM se compara com o LuaJIT por exemplo?

Obrigada! Sua dúvida é super válida e toca num ponto importante. O Lua padrão, especialmente o LuaJIT, é realmente muito rápido. Em muitos casos, chega perto do desempenho do C. Mas a proposta da SolVM é diferente. A gente não busca só performance bruta. O foco é produtividade, portabilidade e extensibilidade. Usar Go como base ajuda muito, porque facilita a distribuição. Dá pra compilar um binário único que roda em qualquer sistema. Além disso, tem integração nativa com ferramentas modernas como HTTP, sistema de arquivos, JSON e por aí vai. Outro ponto é a concorrência com goroutines, que é super simples e poderosa. E como tudo é feito em Go, fica bem mais fácil integrar com outras bibliotecas da linguagem, sem precisar lidar com C ou FFI. É verdade que o LuaJIT é mais rápido em benchmarks pesados, principalmente em código numérico ou com pouca abstração. Mas a SolVM tem outro foco. Queremos ser uma runtime moderna para multifunções, tudo com muito menos fricção. Então, a ideia não é substituir o LuaJIT, e sim oferecer uma alternativa moderna, hackável e acessível. Feita 100% em Go, fácil de portar, entender e modificar.