Cheatsheet Lua
Linguagem de scripting leve para jogos, embedded e automação
Lua
Básico e Tipos
Variáveis e tipos
-- Variáveis locais (recomendado):
local nome = "Ana"
local idade = 30
local pi = 3.14159
local ativo = true
local nada = nil
-- Variáveis globais (evitar!):
contador = 0 -- vai para _G
-- Tipos (type() retorna string):
type("olá") -- "string"
type(42) -- "number"
type(true) -- "boolean"
type(nil) -- "nil"
type({}) -- "table"
type(print) -- "function"Lua tem tipagem dinâmica com 8 tipos: nil, boolean, number, string, table, function, userdata e thread. A keyword local é essencial — variáveis sem ela são globais (armazenadas em _G), causando bugs e performance inferior.
Conversão de tipos
-- tostring (qualquer tipo -> string):
tostring(42) -- "42"
tostring(true) -- "true"
tostring(nil) -- "nil"
-- tonumber (string -> número):
tonumber("42") -- 42
tonumber("3.14") -- 3.14
tonumber("0xFF") -- 255 (hex!)
tonumber("abc") -- nil (falha!)
tonumber("42", 8) -- 34 (base 8)
-- Coerção automática:
"10" + 5 -- 15 (string -> number)
"10" .. 5 -- "105" (number -> string)
-- Verificar antes de usar:
local n = tonumber(input)
if n then
print("Número: " .. n)
endLua faz coerção automática em contextos aritméticos ("10" + 5 = 15) e de concatenação. tonumber() retorna nil quando a conversão falha — sempre verifique antes de usar. tostring() é seguro para qualquer tipo. type() retorna o nome do tipo como string para validação.
Constantes e convenções
-- Lua não tem const nativo -- Convenção: UPPER_CASE para constantes local MAX_SIZE = 100 local PI = 3.14159 local APP_NAME = "MinhaApp" -- Convenções de nomes: local minhaVariavel = 10 -- camelCase local minha_variavel = 10 -- snake_case local _privado = "interno" -- prefixo _ -- Nomes reservados (keywords): -- and, break, do, else, elseif, end -- false, for, function, goto, if -- in, local, nil, not, or, repeat -- return, then, true, until, while -- Evitar: nomes com _ maiúsculo -- (reservado para variáveis internas)
Lua não tem keyword const — use convenção UPPER_CASE para valores que não mudam. Nomes iniciados com _ indicam uso interno/privado por convenção. Keywords como local, function, return não podem ser usados como identificadores. Lua é case-sensitive.
Múltipla atribuição
-- Atribuição simultânea: local x, y, z = 1, 2, 3 local a, b = 10, 20 -- Swap sem variável temporária: a, b = b, a -- a=20, b=10 -- Valores extras são descartados: local m, n = 1, 2, 3 -- 3 ignorado -- Valores faltantes viram nil: local p, q, r = 1, 2 -- r = nil -- Retornos múltiplos de funções: local ok, err = pcall(funcao)
A atribuição múltipla avalia todos os valores à direita antes de atribuir — permite swap elegante sem variável temporária. Valores excedentes são descartados e faltantes recebem nil. É idiomática para capturar retornos múltiplos de funções como pcall e io.open.
Entrada e saída
-- Saída:
print("Olá Mundo") -- com newline
print("x:", 10, "y:", 20) -- tab entre args
io.write("sem newline") -- sem \n
-- Formatação:
print(string.format("%s tem %d anos", nome, 30))
-- Entrada do utilizador:
io.write("Nome: ")
local nome = io.read() -- linha completa
local num = io.read("*n") -- número
local tudo = io.read("*a") -- ficheiro todo
-- Argumentos de linha de comandos:
-- lua script.lua arg1 arg2
print(arg[0]) -- "script.lua"
print(arg[1]) -- "arg1"print() aceita múltiplos argumentos separados por tab e adiciona newline automaticamente. io.write() dá controlo fino (sem newline). io.read() sem argumentos lê uma linha; "*n" parseia número diretamente. A table global arg contém os argumentos CLI.
type() e verificação
-- type() retorna nome do tipo:
type("olá") -- "string"
type(42) -- "number"
type(true) -- "boolean"
type(nil) -- "nil"
type({}) -- "table"
type(print) -- "function"
type(io.open) -- "function"
-- Validação por tipo:
local function processar(x)
if type(x) ~= "number" then
error("espera number, got " .. type(x))
end
return x * 2
end
-- Dispatch por tipo:
local function descrever(v)
local t = type(v)
if t == "number" then return "num: " .. v
elseif t == "string" then return "str: " .. v
else return t end
endtype() retorna o nome do tipo como string — essencial para validação e dispatch. Combine com error() para falhar rápido com mensagem clara. Para verificar se é inteiro em Lua 5.3+, use math.type(x) == "integer". tostring() e tonumber() complementam a introspeção de tipos.
Números e matemática
-- Literais: local dec = 42 local float = 3.14 local hex = 0xFF -- 255 local exp = 1.5e3 -- 1500.0 -- Operadores (Lua 5.3+): 10 / 3 -- 3.333... (sempre float!) 10 // 3 -- 3 (divisão inteira) 10 % 3 -- 1 (módulo) 10 ^ 3 -- 1000.0 (potência, float) -- math library: math.floor(3.7) -- 3 math.ceil(3.2) -- 4 math.abs(-5) -- 5 math.max(1, 5, 3) -- 5 math.sqrt(16) -- 4.0 math.random(1, 100) -- inteiro 1-100 math.pi -- 3.14159...
Em Lua, / é sempre divisão float — para divisão inteira use // (Lua 5.3+). ^ retorna sempre float. Desde Lua 5.3 existem subtipos integer (64-bit) e float (double), com conversão automática. Use math.randomseed para reprodutibilidade.
Escopo e blocos
-- Blocos (do...end): do local temp = 42 print(temp) -- 42 end print(temp) -- nil! (fora de scope) -- Shadowing: local x = 1 do local x = 2 -- novo x, esconde outer print(x) -- 2 end print(x) -- 1 -- Upvalues (base de closures): local contador = 0 local function inc() contador = contador + 1 -- upvalue end -- _ENV (ambiente em Lua 5.2+): -- globais são campos de _ENV
Lua usa scope lexical com blocos delimitados por do...end, if...end, for...end. local dentro de um bloco morre no fim — previne poluição do namespace. Upvalues são variáveis locais capturadas por funções aninhadas. Desde Lua 5.2, _ENV permite sandboxing.
Strings básicas
-- Aspas simples ou duplas:
local s1 = "Olá Mundo"
local s2 = 'Também funciona'
-- String longa (multi-linha):
local texto = [[
Linha 1
Linha 2 sem escape
]]
-- Concatenação (..):
local msg = "Olá, " .. nome .. "!"
local n = "idade: " .. 30 -- auto-convert
-- Comprimento:
#s1 -- 10 (bytes)
-- Repetição:
string.rep("ab", 3) -- "ababab"Strings em Lua são imutáveis — operações criam novas strings. O operador .. concatena e converte números automaticamente. Strings longas [[ ]] preservam formatação sem escapes — ideais para templates. #string retorna o comprimento em bytes (não caracteres UTF-8).
nil e truthiness
-- nil: ausência de valor
local x = nil
print(x) -- nil
-- FALSO em Lua: SÓ nil e false!
if nil then end -- não executa
if false then end -- não executa
-- VERDADEIRO: tudo o resto!
if 0 then print("0 é true!") end
if "" then print("vazio é true!") end
if {} then print("{} é true!") end
-- nil como "remoção":
local t = {a = 1, b = 2}
t.a = nil -- "remove" campo a
-- Verificar existência:
if t.b ~= nil then
print("b existe")
endEm Lua, apenas nil e false são falsy — 0, string vazia e table vazia são TODOS verdadeiros (diferente de Python/JS). nil é usado para "remover" campos de tables e indicar ausência de valor. Sempre use ~= nil para verificar existência explícita.
Funções
Definição e chamada
-- Função local (recomendado):
local function saudacao(nome)
return "Olá, " .. nome .. "!"
end
print(saudacao("Ana")) -- "Olá, Ana!"
-- Sintaxe alternativa:
local dobro = function(x)
return x * 2
end
-- Sem return: retorna nil
local function log(msg)
print("[LOG] " .. msg)
end
-- Função global (evitar):
function global_fn()
return 42
end
-- Chamada com sintaxe especial:
print "Olá" -- sem parênteses (1 arg string)
dofile "script.lua"Funções em Lua são valores de primeira classe — podem ser armazenadas, passadas e retornadas. local function é preferível a global. Sem return explícito, a função retorna nil. Lua permite omitir parênteses quando o único argumento é uma string literal ou table constructor: print "Olá".
Funções como valores
-- Dispatch table:
local ops = {
soma = function(a, b) return a + b end,
sub = function(a, b) return a - b end,
mul = function(a, b) return a * b end,
}
print(ops.soma(3, 4)) -- 7
-- Higher-order function:
local function aplicar(fn, lista)
local resultado = {}
for i, v in ipairs(lista) do
resultado[i] = fn(v)
end
return resultado
end
local dobros = aplicar(function(x)
return x * 2
end, {1, 2, 3})
-- Callback em sort:
table.sort(nums, function(a, b)
return a > b -- descendente
end)Funções como valores de primeira classe permitem dispatch tables, higher-order functions e callbacks. table.sort com comparador custom é o exemplo mais comum. A sintaxe obj:metodo() é açúcar para obj.metodo(obj) — passa self automaticamente. Funções anónimas inline são ubíquas em Lua.
Métodos e self (:)
-- Definição com : (self implícito):
local Conta = {}
Conta.__index = Conta
function Conta.new(saldo)
return setmetatable({saldo = saldo}, Conta)
end
-- : passa self automaticamente:
function Conta:depositar(v)
self.saldo = self.saldo + v
end
function Conta:get_saldo()
return self.saldo
end
-- Chamada com :
local c = Conta.new(100)
c:depositar(50)
print(c:get_saldo()) -- 150
-- Equivalência:
-- c:depositar(50) == c.depositar(c, 50)
-- function Conta:fn() == function Conta.fn(self)A sintaxe : (dois pontos) passa self automaticamente: obj:metodo() equivale a obj.metodo(obj). Na definição, function Obj:fn() equivale a function Obj.fn(self). É a convenção universal para métodos em Lua — sem :, self seria nil e o método falharia.
Múltiplos retornos
-- Retornar múltiplos valores:
local function dividir(a, b)
if b == 0 then
return nil, "Divisão por zero"
end
return a / b, nil
end
local resultado, erro = dividir(10, 3)
if erro then
print("Erro: " .. erro)
else
print("Resultado: " .. resultado)
end
-- Padrão io.open:
local f, err = io.open("file.txt", "r")
if not f then
print("Falha: " .. err)
return
end
-- Ajustar número de retornos:
local a, b = funcao() -- captura 2
local x = funcao() -- captura só 1
local t = {funcao()} -- captura todosMúltiplos retornos são idiomáticos em Lua: o padrão valor, erro (nil + mensagem em falha) evita exceções para erros esperados. io.open, pcall e string.find usam este padrão. Atribuição captura exatamente o número de variáveis — use {fn()} para capturar todos numa table.
Recursão e tail calls
-- Recursão simples: local function fatorial(n) if n <= 1 then return 1 end return n * fatorial(n - 1) end -- Tail call (otimizada por Lua!): local function fat_tail(n, acc) acc = acc or 1 if n <= 1 then return acc end return fat_tail(n - 1, n * acc) -- ^ tail call: sem stack extra! end -- NÃO é tail call (operação após): local function fat_bad(n) if n <= 1 then return 1 end return n * fat_bad(n - 1) -- * depois! end -- print(fat_tail(100000)) -- OK! -- print(fat_bad(100000)) -- stack overflow!
Lua otimiza tail calls: se a última ação é return func(args), a stack frame é reutilizada — recursão infinita sem overflow. Para ser tail call, o return deve ser EXATAMENTE a chamada — sem operações posteriores. Isto permite loops via recursão com performance de iteração. Essencial para algoritmos recursivos profundos.
Varargs (...)
-- Número variável de argumentos:
local function soma(...)
local args = {...} -- table dos args
local total = 0
for _, v in ipairs(args) do
total = total + v
end
return total
end
print(soma(1, 2, 3, 4)) -- 10
-- select para aceder a ...:
local n = select("#", ...) -- count seguro
local primeiro = select(1, ...) -- 1º arg
-- table.pack preserva nils (5.3+):
local safe = table.pack(...)
print(safe.n) -- count real
-- Repassar argumentos:
local function log(fn, ...)
print("chamando...")
return fn(...) -- repassa todos
end
-- printf-style:
local function printf(fmt, ...)
io.write(string.format(fmt, ...))
end... (varargs) captura argumentos extras. {...} cria table mas perde nils trailing — use select("#", ...) ou table.pack para count seguro. fn(...) repassa todos os argumentos. O padrão string.format(fmt, ...) é a base de funções printf-style em Lua.
Iteradores customizados
-- Iterator com closure:
local function range(n)
local i = 0
return function()
i = i + 1
if i <= n then return i end
end
end
for i in range(5) do
print(i) -- 1, 2, 3, 4, 5
end
-- Pares ordenados:
local function sorted_pairs(t)
local keys = {}
for k in pairs(t) do
keys[#keys + 1] = k
end
table.sort(keys)
local i = 0
return function()
i = i + 1
local k = keys[i]
if k then return k, t[k] end
end
end
for k, v in sorted_pairs(config) do
print(k, v)
endIteradores em Lua são funções que retornam o próximo valor a cada chamada — quando retornam nil, o for genérico para. Closures com estado local são o padrão mais comum. O for genérico chama o iterador repetidamente até nil. Permitem sequências infinitas, filtros e transformações lazy.
Closures
-- Closure: função + ambiente capturado
local function criar_contador()
local count = 0 -- upvalue
return function()
count = count + 1
return count
end
end
local c1 = criar_contador()
local c2 = criar_contador()
print(c1()) -- 1
print(c1()) -- 2
print(c2()) -- 1 (independente!)
-- Closure com parâmetros:
local function multiplicador(fator)
return function(x)
return x * fator
end
end
local dobro = multiplicador(2)
local triplo = multiplicador(3)
print(dobro(5)) -- 10
print(triplo(5)) -- 15Closures capturam upvalues (variáveis locais do scope exterior) por referência — cada chamada cria um ambiente independente. Permitem estado privado sem classes: variáveis locais são inacessíveis externamente. São a principal ferramenta de encapsulamento em Lua e a base de callbacks, iterators e módulos.
pcall e xpcall
-- pcall (protected call):
local ok, resultado = pcall(function()
return operacao_perigosa()
end)
if ok then
print("Sucesso: " .. resultado)
else
print("Erro: " .. resultado)
end
-- pcall com função direta:
local ok, err = pcall(io.open, "file.txt")
-- xpcall com handler custom:
local ok, err = xpcall(funcao, function(e)
return e .. "\n" .. debug.traceback("", 2)
end)
-- assert (error se falso):
local f = assert(io.open("dados.txt"))
local n = assert(tonumber(input), "Não é número")
-- error() lança erro:
if idade < 0 then
error("Idade negativa", 2)
endpcall executa em modo protegido: retorna true+resultado em sucesso, false+mensagem em erro — é o try/catch de Lua. xpcall adiciona handler custom (ex: stack trace via debug.traceback). assert() é açúcar para "se falso, error()". Use error() para erros de programação.
Controlo de Fluxo
If / Elseif / Else
-- Condicional básico:
if nota >= 18 then
print("Excelente")
elseif nota >= 10 then
print("Aprovado")
else
print("Reprovado")
end
-- elseif é keyword única (não else if)
-- Sem switch/case nativo:
-- Use if/elseif ou dispatch table:
local acoes = {
["start"] = function() iniciar() end,
["stop"] = function() parar() end,
}
local fn = acoes[comando]
if fn then fn() endelseif é uma keyword única em Lua (não else if separado). Não há switch/case nativo — use cadeias if/elseif ou dispatch tables (mapear comandos a funções). A condição pode ser qualquer valor: só nil e false não executam o bloco.
For genérico (ipairs/pairs)
-- ipairs: arrays em ordem sequencial
local frutas = {"maçã", "banana", "uva"}
for i, v in ipairs(frutas) do
print(i, v) -- 1 maçã, 2 banana...
end
-- PARA no primeiro nil!
-- pairs: todos os pares (sem ordem)
local pessoa = {nome="Ana", idade=30}
for k, v in pairs(pessoa) do
print(k, v)
end
-- Iterar só chaves:
for k in pairs(pessoa) do
print(k)
end
-- next() manualmente:
local k, v = next(pessoa)
-- Iterar ficheiro linha-a-linha:
for linha in io.lines("dados.txt") do
print(linha)
endipairs itera arrays na ordem 1,2,3... e PARA no primeiro nil — ideal para listas sequenciais. pairs itera TODOS os pares chave-valor sem ordem garantida. O for genérico aceita qualquer iterador: io.lines, string.gmatch, coroutines. A variável de iteração é local e read-only.
Dispatch tables
-- Substitui switch/case:
local handlers = {
["GET"] = function(req) return listar(req) end,
["POST"] = function(req) return criar(req) end,
["PUT"] = function(req) return atualizar(req) end,
["DELETE"] = function(req) return remover(req) end,
}
local function router(req)
local handler = handlers[req.method]
if not handler then
return 405, "Method not allowed"
end
return handler(req)
end
-- Com default:
local resultado = (handlers[cmd] or function()
print("Comando desconhecido: " .. cmd)
end)(arg)
-- Registo dinâmico:
handlers["PATCH"] = function(req)
return patch(req)
endDispatch tables mapeiam chaves a funções — substituem switch/case com vantagens: registo dinâmico, O(1) lookup, extensibilidade. O padrão (handlers[cmd] or default)(args) executa com fallback. São ubíquas em Lua: routers HTTP, parsers, command patterns e state machines.
While
-- while: condição ANTES do corpo
local n = 10
while n > 0 do
print(n)
n = n - 1
end
-- Loop infinito (game loop, servidor):
while true do
local evento = esperar_evento()
if evento == "sair" then break end
processar(evento)
end
-- while com múltiplas condições:
local i, j = 1, 100
while i < j do
i = i + 1
j = j - 1
end
-- Cuidado: pode nunca executar
local x = 0
while x > 10 do
print("nunca aparece")
endwhile verifica a condição ANTES de executar — o corpo pode nunca executar se a condição for falsa inicialmente. Para loops infinitos, use while true do com break para sair. A condição é reavaliada a cada iteração. Variáveis do corpo são locais a cada iteração.
Break e goto
-- break: sai do loop mais interno
for i = 1, 100 do
if i * i > 50 then
print("encontrado: " .. i)
break
end
end
-- break deve ser última statement:
while true do
if condicao then
break -- OK (fim do bloco if)
end
end
-- goto (Lua 5.2+): sair de loops aninhados
for i = 1, 10 do
for j = 1, 10 do
if i * j > 50 then
goto found
end
end
end
::found::
print("Encontrado!")
-- goto para retry:
::tentar::
local ok, err = pcall(operacao)
if not ok then goto tentar endbreak só pode ser a última statement de um bloco — sai apenas do loop mais interno. goto (Lua 5.2+) permite saltar para labels ::nome:: — útil para break de loops aninhados e padrões de retry. Labels não podem saltar sobre declarações local. Use com moderação.
Repeat-Until
-- repeat-until: condição DEPOIS do corpo
-- Executa pelo menos 1 vez!
local input
repeat
io.write("Senha: ")
input = io.read()
until input == "secreta"
-- Vantagem: variáveis visíveis no until
repeat
local x = calcular()
until x > 100 -- x visível aqui!
-- while equivalente precisaria:
local x
while true do
x = calcular()
if x > 100 then break end
end
-- until inverte: para quando TRUE
-- (continua enquanto FALSE)repeat-until executa pelo menos uma vez (condição verificada DEPOIS). Grande vantagem: variáveis declaradas no corpo são visíveis na condição until. A lógica é invertida — o loop PARA quando a condição é verdadeira. Ideal para validação de input e loops "faça até conseguir".
Padrões de loop
-- Acumulador:
local soma = 0
for i = 1, 100 do
soma = soma + i
end
-- Busca com flag:
local encontrado = false
for _, v in ipairs(lista) do
if v == alvo then
encontrado = true
break
end
end
-- Filter manual:
local pares = {}
for _, v in ipairs(nums) do
if v % 2 == 0 then
pares[#pares + 1] = v
end
end
-- Loop com índice e valor:
for i, v in ipairs(items) do
print(i .. ": " .. v)
end
-- Countdown com step:
for i = #t, 1, -1 do
processar(t[i])
endPadrões comuns: acumulador (soma/concatenação), busca com break, filter manual com #t+1 para append. Iterar de trás para frente (for i = #t, 1, -1) é seguro para remoção durante iteração. Lua não tem map/filter/reduce built-in — implementam-se com loops simples.
For numérico
-- for i = início, fim, passo:
for i = 1, 10 do
print(i) -- 1, 2, ..., 10
end
-- Com passo negativo (countdown):
for i = 10, 1, -1 do
print(i)
end
-- Com passo decimal:
for i = 0, 1, 0.1 do
print(string.format("%.1f", i))
end
-- A variável é LOCAL e read-only:
for i = 1, 5 do
i = i * 2 -- NÃO afeta o loop!
end
-- início, fim, passo avaliados 1 vez:
local limite = 10
for i = 1, limite do
limite = 0 -- não afeta iterações
endO for numérico tem a forma for i = início, fim, passo do. O passo default é 1; pode ser negativo ou decimal. A variável de iteração é local e read-only — reatribuir não afeta o loop. Início, fim e passo são avaliados UMA vez antes de iniciar. O loop inclui o valor final (é inclusivo).
Guard clauses e early return
-- Em vez de nesting profundo:
local function processar(dados)
if not dados then return nil, "sem dados" end
if #dados == 0 then return {} end
if type(dados[1]) ~= "number" then
return nil, "tipo inválido"
end
-- Lógica principal (sem nesting):
local resultado = {}
for _, v in ipairs(dados) do
resultado[#resultado + 1] = v * 2
end
return resultado
end
-- Validação com assert:
local function dividir(a, b)
assert(type(a) == "number", "a deve ser number")
assert(b ~= 0, "divisão por zero")
return a / b
endGuard clauses verificam condições de erro no início e fazem return cedo — evitam nesting profundo e tornam o código mais legível. O padrão return nil, msg para erros operacionais é idiomático. assert() valida pré-condições de programação (falha rápido). Combine com pcall no chamador para capturar.
Strings e Patterns
string library
-- Operações básicas:
local s = "Olá Mundo"
string.upper(s) -- "OLÁ MUNDO"
string.lower(s) -- "olá mundo"
string.len(s) -- 11 (bytes)
string.rep("ab", 3) -- "ababab"
string.reverse("abc") -- "cba"
-- Substring:
string.sub(s, 1, 3) -- "Olá"
string.sub(s, 5) -- "Mundo" (até fim)
string.sub(s, -5) -- "Mundo" (do fim)
-- Byte/char:
string.byte("A") -- 65
string.char(65, 66) -- "AB"
-- Método com : (sugar):
local u = s:upper() -- equivalente
local p = s:sub(1, 3)A biblioteca string oferece operações imutáveis — todas retornam nova string. Pode ser chamada como função (string.upper(s)) ou método (s:upper()). string.sub aceita índices negativos (do fim). string.byte/string.char convertem entre bytes e caracteres. Strings são sempre imutáveis em Lua.
string.gsub
-- Substituição global:
local s = "Olá Mundo Mundo"
local r, n = string.gsub(s, "Mundo", "Lua")
print(r) -- "Olá Lua Lua"
print(n) -- 2 (substituições)
-- Limitar substituições:
string.gsub(s, "Mundo", "Lua", 1)
-- "Olá Lua Mundo" (só 1ª)
-- Com patterns:
string.gsub("a1b2c3", "%d", "#")
-- "a#b#c#"
-- Com função (transformação):
string.gsub("hello world", "%a+", function(w)
return w:upper()
end)
-- "HELLO WORLD"
-- Com table (lookup):
local entities = {lt="<", gt=">", amp="&"}
string.gsub("<>", "&(%a+);", entities)
-- "<>"string.gsub substitui todas as ocorrências e retorna a string + count. O 4º argumento limita substituições. Pode receber função (resultado da função substitui) ou table (chave → valor) como reposição. É a ferramenta mais poderosa de transformação de strings em Lua — substitui regex replace de outras linguagens.
string.format
-- Formatação estilo printf:
string.format("%s tem %d anos", "Ana", 30)
-- "Ana tem 30 anos"
-- Especificadores:
string.format("%.2f", 3.14159) -- "3.14"
string.format("%10s", "Lua") -- " Lua"
string.format("%-10s|", "Lua") -- "Lua |"
string.format("%05d", 42) -- "00042"
string.format("%x", 255) -- "ff"
string.format("%X", 255) -- "FF"
string.format("%o", 8) -- "10" (octal)
string.format("%c", 65) -- "A"
string.format("%q", 'he said "hi"')
-- "he said \"hi\"" (escaped)
-- %% para percentagem literal:
string.format("%.1f%%", 85.678) -- "85.7%"
-- %d só integers (5.3+):
string.format("%d", 3.7) -- ERRO!string.format é o printf de Lua: %d inteiro, %s string, %f float, %x hex, %c char. %.2f limita casas decimais. %q escapa para literal Lua seguro. %% para percentagem literal. Em Lua 5.3+, %d exige integer — use %f ou math.floor para floats.
Capturas e parsing
-- Capturas com parênteses:
local key, val = string.match(
"nome=Ana", "(%a+)=(%a+)")
print(key, val) -- "nome", "Ana"
-- Parsing de URL:
local proto, host, path = string.match(
"https://site.com/page",
"(%a+)://([^/]+)(.*)")
-- CSV simples:
for campo in string.gmatch("a,b,c", "[^,]+") do
print(campo) -- a, b, c
end
-- gmatch: iterador de matches
for word in string.gmatch("one two three", "%a+") do
print(word)
end
-- Captura posicional:
local h, m = string.match("14:30", "(%d+):(%d+)")
print(h, m) -- 14, 30
-- %0 captura o match inteiro:
string.gsub("abc", "(%a)", "[%0]")
-- "[a][b][c]"Capturas com () extraem partes do match — string.match retorna todas as capturas. string.gmatch é um iterador sobre todos os matches (ideal para parsing de CSV, tokens). %0 refere o match inteiro. Patterns como [^,]+ (um ou mais não-vírgula) são comuns para splitting. Mais simples que regex para casos típicos.
string.find e match
-- find: retorna posições (ou nil)
local ini, fim = string.find("Olá Mundo", "Mundo")
print(ini, fim) -- 5, 9
-- Com patterns:
local i, j = string.find("preço: 42", "%d+")
print(i, j) -- 8, 9
-- match: extrai o padrão encontrado
local num = string.match("idade: 30", "%d+")
print(num) -- "30"
-- Múltiplas capturas:
local d, m, a = string.match(
"25/12/2024", "(%d+)/(%d+)/(%d+)")
print(d, m, a) -- 25, 12, 2024
-- Sem match: retorna nil
local r = string.match("abc", "%d+")
print(r) -- nil
-- plain text (sem patterns):
string.find("a.b", ".", 1, true) -- 2, 2string.find retorna posições início/fim (ou nil). string.match extrai o conteúdo encontrado — mais conveniente para parsing. Capturas com () retornam múltiplos valores. O 4º argumento true em find desativa patterns (busca literal). Ambos aceitam posição inicial como argumento extra.
string.gmatch e split
-- gmatch: iterador de padrões
for word in string.gmatch("um dois três", "%S+") do
print(word)
end
-- Split por delimitador:
local function split(s, sep)
local parts = {}
for part in string.gmatch(s, "[^" .. sep .. "]+") do
parts[#parts + 1] = part
end
return parts
end
local t = split("a,b,c", ",")
-- {"a", "b", "c"}
-- Tokenizer simples:
for token in string.gmatch(code, "%a+%d*") do
print(token)
end
-- Extrair todos os números:
local nums = {}
for n in string.gmatch("x1 y22 z333", "%d+") do
nums[#nums + 1] = tonumber(n)
end
-- {1, 22, 333}string.gmatch retorna um iterador sobre todos os matches — ideal para splitting, tokenizing e extração múltipla. O padrão [^sep]+ (não-delimitador) é a base de funções split. %S+ captura palavras (não-espaço). Diferente de match (só primeiro), gmatch percorre a string inteira. Essencial para parsing em Lua.
Patterns em Lua
-- Patterns (NÃO são regex!):
-- %a = letra, %d = dígito, %s = espaço
-- %w = alfanumérico, %p = pontuação
-- %u = maiúscula, %l = minúscula
-- Quantificadores:
-- + = 1 ou mais (greedy)
-- * = 0 ou mais (greedy)
-- - = 0 ou mais (lazy)
-- ? = 0 ou 1
-- Âncoras:
-- ^ = início da string
-- $ = fim da string
-- Exemplos:
string.match("abc123", "%a+") -- "abc"
string.match("abc123", "%d+") -- "123"
string.match("a.b", "%.") -- "." (escape)
string.match("hello", "^h") -- "h"
string.match("file.lua", "%.%w+$") -- "lua"
-- Classes maiúsculas = complemento:
-- %A = não-letra, %D = não-dígitoPatterns de Lua são mais simples que regex: %a letra, %d dígito, %s espaço, %w alfanumérico. Quantificadores: + (greedy), - (lazy), *, ?. % escapa caracteres especiais. Classes maiúsculas são complementos (%D = não-dígito). Não suportam alternância (|) nem grupos complexos.
utf8 (Lua 5.3+)
-- # retorna BYTES, não caracteres:
#"olá" -- 4 (á = 2 bytes UTF-8)
-- utf8 library (5.3+):
local utf8 = require("utf8") -- ou built-in
-- Comprimento em caracteres:
utf8.len("olá") -- 3
-- Iterar codepoints:
for pos, code in utf8.codes("olá") do
print(pos, code)
end
-- Converter:
utf8.char(65, 66) -- "AB"
utf8.codepoint("á") -- 225
-- Offset (posição do byte):
utf8.offset("olá", 3) -- byte pos do 3º char
-- Substring por caractere:
local function usub(s, i, j)
local pi = utf8.offset(s, i)
local pj = utf8.offset(s, j + 1)
return s:sub(pi, pj - 1)
endEm Lua, # retorna bytes — para contar caracteres UTF-8 use utf8.len() (Lua 5.3+). utf8.codes() itera codepoints com posições. utf8.offset() converte posição de caractere para byte (necessário para string.sub). Sem a biblioteca utf8, operações como upper/lower podem falhar em caracteres acentuados.
Avançado
Coroutines
-- Criar e executar:
local co = coroutine.create(function(a, b)
print("Início:", a, b)
local c = coroutine.yield(a + b)
print("Recebi:", c)
return a * b
end)
print(coroutine.resume(co, 3, 4))
-- "Início: 3 4", true, 7
print(coroutine.resume(co, 10))
-- "Recebi: 10", true, 12
print(coroutine.status(co)) -- "dead"
-- coroutine.wrap (generator simples):
local function fibonacci()
return coroutine.wrap(function()
local a, b = 0, 1
while true do
coroutine.yield(a)
a, b = b, a + b
end
end)
end
for n in fibonacci() do
if n > 100 then break end
print(n)
endCoroutines são funções que pausam (yield) e retomam (resume) — cooperativas, não paralelas. coroutine.create + resume/yield dão controlo total; coroutine.wrap retorna função iterável (mais simples para generators). Estados: suspended, running, dead. Usos: generators infinitos, state machines, async simulado.
Metaprogramação
-- __index como função (properties):
local Config = setmetatable({}, {
__index = function(t, key)
local env = os.getenv(key:upper())
return env or "default_" .. key
end
})
print(Config.database_url) -- lê ENV
-- __newindex (validação):
local Strict = setmetatable({}, {
__newindex = function(t, k, v)
if type(v) ~= "number" then
error(k .. " deve ser número")
end
rawset(t, k, v)
end
})
Strict.x = 42 -- OK
Strict.y = "abc" -- ERRO!
-- DSL com __index + __call:
local html = setmetatable({}, {
__index = function(_, tag)
return function(content)
return "<" .. tag .. ">" .. content
.. "</" .. tag .. ">"
end
end
})
print(html.div("Olá")) -- <div>Olá</div>Metaprogramação via metamethods: __index como função cria computed properties, __newindex valida escritas, __call transforma tables em DSLs callable. rawset/rawget são essenciais dentro de metamethods para evitar recursão infinita. Este mecanismo permite OOP, frameworks e DSLs expressivas em Lua.
Módulos e require
-- Ficheiro: utils.lua
local M = {}
local function privado() -- não exportado
return "interno"
end
function M.soma(a, b)
return a + b
end
return M -- OBRIGATÓRIO retornar
-- Uso noutro ficheiro:
local utils = require("utils")
print(utils.soma(3, 4)) -- 7
-- require é cacheado (singleton):
local u1 = require("utils")
local u2 = require("utils")
print(u1 == u2) -- true (mesma table)
-- package.path (onde procura):
print(package.path) -- "./?.lua;..."
-- Submódulos:
local json = require("cjson")require carrega e executa um módulo UMA vez (cacheado em package.loaded) — chamadas subsequentes retornam a mesma table. O módulo DEVE retornar uma table ou função. Variáveis local são privadas — só o exportado na table é público. package.path define onde procura (? = nome do módulo).
C API e FFI
-- Lua C API (extensões nativas):
-- Ficheiro mylib.c:
-- #include <lua.h>
-- #include <lauxlib.h>
--
-- static int l_soma(lua_State *L) {
-- double a = luaL_checknumber(L, 1);
-- double b = luaL_checknumber(L, 2);
-- lua_pushnumber(L, a + b);
-- return 1;
-- }
--
-- int luaopen_mylib(lua_State *L) {
-- lua_register(L, "soma", l_soma);
-- return 0;
-- }
-- LuaJIT FFI (sem C!):
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Olá %s!\n", "Lua")
-- Carregar módulo C:
local mylib = require("mylib")A C API permite estender Lua com código nativo — funções C recebem lua_State (stack virtual), leem args com luaL_check*, retornam via lua_push*. luaopen_nome é o entry point do require. LuaJIT FFI elimina C: ffi.cdef declara assinaturas e ffi.C chama funções do sistema. Lua foi desenhada para embedding.
File I/O
-- Ler ficheiro completo:
local f = assert(io.open("dados.txt", "r"))
local conteudo = f:read("*a")
f:close()
-- Ler linha-a-linha:
for linha in io.lines("dados.txt") do
print(linha)
end
-- Escrever:
local out = assert(io.open("saida.txt", "w"))
out:write("Linha 1\n")
out:write(string.format("Valor: %d\n", 42))
out:close()
-- Append:
local log = io.open("app.log", "a")
log:write(os.date() .. " evento\n")
log:close()
-- Modos: r, w, a, r+, w+, a+
-- rb, wb (binário em Windows)
-- Verificar existência:
local function existe(path)
local f = io.open(path, "r")
if f then f:close() return true end
return false
endio.open retorna file handle ou nil + erro — sempre use assert() ou verifique. io.lines() é o iterador idiomático para leitura linha-a-linha (fecha automaticamente). Modos: "r" leitura, "w" escrita (trunca), "a" append. Em Windows, adicione "b" para binário.
Debug e profiling
-- debug library:
local info = debug.getinfo(1)
print(info.source) -- "@script.lua"
print(info.currentline) -- linha atual
-- Stack trace:
print(debug.traceback("mensagem"))
-- Hooks (monitorizar execução):
debug.sethook(function(event, line)
print(event, line)
end, "l") -- "l" = cada linha
-- Inspecionar upvalues:
local f = function()
local x = 42
return x
end
local name, val = debug.getupvalue(f, 1)
print(name, val) -- "x", 42
-- Profiling simples:
local function profile(fn, ...)
local start = os.clock()
local results = {fn(...)}
print(string.format("%.4fs", os.clock() - start))
return table.unpack(results)
end
-- Memória:
collectgarbage("count") -- KB usadosA biblioteca debug permite introspeção: getinfo retorna fonte/linha, traceback gera stack traces, getupvalue/getlocal inspecionam variáveis. sethook permite profilers (evento "l" = cada linha, "c" = call). os.clock() mede CPU time. collectgarbage("count") reporta memória em KB.
Error handling avançado
-- Padrão result/error (Go-style):
local function ler_ficheiro(path)
local f, err = io.open(path, "r")
if not f then
return nil, "Não abri: " .. err
end
local content = f:read("*a")
f:close()
return content
end
local data, err = ler_ficheiro("config.json")
if not data then
print("Erro: " .. err)
return
end
-- Error objects (estruturados):
error({code = 404, msg = "Not found"})
-- Retry com pcall:
local function com_retry(fn, tentativas)
tentativas = tentativas or 3
for i = 1, tentativas do
local ok, result = pcall(fn)
if ok then return result end
if i == tentativas then error(result) end
end
endLua tem dois estilos: exceções (error/pcall) para erros de programação, e return nil, msg para erros operacionais. O estilo nil+msg é idiomático em I/O. Error objects (tables com code/msg) permitem erros estruturados. com_retry encapsula tentativas com pcall — padrão comum em rede.
OS e ambiente
-- Data e hora:
os.time() -- timestamp Unix
os.date("%Y-%m-%d") -- "2024-12-25"
os.date("*t") -- table com campos
os.clock() -- CPU time (segundos)
os.difftime(t2, t1) -- diferença
-- Ambiente:
os.getenv("HOME") -- variável de ambiente
os.getenv("PATH")
-- Sistema:
os.execute("ls -la") -- executar comando
os.remove("temp.txt") -- apagar ficheiro
os.rename("a.txt", "b.txt")
os.tmpname() -- nome temp único
os.exit(0) -- terminar programa
-- Tabela de data:
local t = os.date("*t")
print(t.year, t.month, t.day)
print(t.hour, t.min, t.sec)
-- Timestamp de data específica:
local ts = os.time({year=2024, month=12, day=25})os.time() retorna timestamp Unix; os.date() formata (com "*t" retorna table com campos). os.clock() mede CPU time para profiling. os.getenv() lê variáveis de ambiente. os.execute() executa comandos do sistema. Para operações avançadas de filesystem (mkdir, glob), use LuaFileSystem (lfs) via luarocks.
Operadores
Operadores aritméticos
-- Básicos: 10 + 3 -- 13 10 - 3 -- 7 10 * 3 -- 30 10 / 3 -- 3.333... (float!) -10 -- negação unária -- Lua 5.3+: 10 // 3 -- 3 (divisão inteira) 10 % 3 -- 1 (módulo/resto) 10 ^ 3 -- 1000.0 (potência) -- Módulo com negativos: -7 % 3 -- 2 (resultado positivo) 7 % -3 -- -2 (sinal do divisor) -- Integer vs float: local a = 10 // 3 -- integer 3 local b = 10 / 3 -- float 3.333 local c = 2 ^ 10 -- float 1024.0
/ é sempre float em Lua; // faz divisão inteira (Lua 5.3+). % segue o sinal do divisor. ^ retorna sempre float e é right-associative (2^3^2 = 2^9 = 512). Operações entre integers permanecem integer exceto / e ^.
Precedência de operadores
-- Ordem (alta -> baixa):
-- ^
-- not # - (unário)
-- * / // %
-- + -
-- ..
-- < > <= >= ~= ==
-- and
-- or
-- Exemplos:
2 + 3 * 4 -- 14 (não 20)
2 ^ 3 ^ 2 -- 512 (right-assoc)
-2 ^ 2 -- -4 (unário depois de ^)
not nil and 42 -- 42
-- Use parênteses para clareza:
local x = (a + b) * c
local y = not (x == 0)
-- .. é right-associative:
"a" .. "b" .. "c" -- "a" .. ("b" .. "c")^ tem maior precedência e é right-associative. O unário - tem precedência menor que ^ (-2^2 = -4). .. também é right-associative. and tem precedência maior que or. Na dúvida, use parênteses — tornam a intenção clara sem custo de performance.
rawget, rawset e rawequal
-- rawget: leitura sem metamethods
local t = setmetatable({}, {
__index = function() return "default" end
})
t.x -- "default" (via __index)
rawget(t, "x") -- nil (ignora __index)
-- rawset: escrita sem metamethods
local strict = setmetatable({}, {
__newindex = function()
error("proibido!")
end
})
strict.x = 1 -- ERRO!
rawset(strict, "x", 1) -- OK (ignora)
-- rawequal: comparação sem __eq
local mt = {__eq = function() return true end}
local a = setmetatable({}, mt)
local b = setmetatable({}, mt)
a == b -- true (via __eq)
rawequal(a, b) -- false (referência)
-- rawlen (5.2+): # sem __len
rawlen("texto") -- 5Funções raw* contornam metamethods: rawget/rawset são essenciais dentro de __index/__newindex para evitar recursão infinita. rawequal compara por referência ignorando __eq. rawlen (5.2+) ignora __len. Use-as apenas quando precisar de aceder à table "real".
Operadores relacionais
-- Comparações (retornam boolean):
10 == 10 -- true (igual)
10 ~= 5 -- true (DIFERENTE, não !=)
10 < 20 -- true
10 > 5 -- true
10 <= 10 -- true
10 >= 11 -- false
-- NOTA: ~= é "diferente" em Lua!
-- (não existe !=)
-- Comparação de strings (lexicográfica):
"abc" < "abd" -- true
"Lua" == "Lua" -- true
-- Tables/functions: só == e ~=
-- (comparam referência, não conteúdo)
local a = {}
local b = {}
a == b -- false (objetos diferentes)
a == a -- true (mesma referência)O operador de diferença em Lua é ~= (não !=). Comparações < e > funcionam para números e strings (ordem lexicográfica). Tables e functions só podem ser comparadas com ==/~= por referência — para comparação por valor, use metamethods __eq, __lt.
Operadores bitwise (5.3+)
-- Bitwise (Lua 5.3+):
0xFF & 0x0F -- 15 (AND)
0xF0 | 0x0F -- 255 (OR)
0xFF ~ 0x0F -- 240 (XOR)
~0xFF -- -256 (NOT, complemento)
-- Shifts:
1 << 4 -- 16 (shift left)
256 >> 4 -- 16 (shift right)
-- Aplicações:
-- Flags/permissions:
local READ = 1 -- 001
local WRITE = 2 -- 010
local EXEC = 4 -- 100
local perms = READ | WRITE -- 3 (011)
-- Verificar flag:
if perms & READ ~= 0 then
print("tem leitura")
endOperadores bitwise existem desde Lua 5.3: & (AND), | (OR), ~ (XOR binário / NOT unário), << e >> (shifts). Operam apenas em integers. Úteis para flags, permissões e manipulação de bits. Em Lua 5.1/5.2 use bit32 ou bit (LuaJIT).
Operadores lógicos
-- and: retorna 1º falso ou último:
true and 42 -- 42
nil and 42 -- nil
1 and 2 and 3 -- 3
-- or: retorna 1º verdadeiro ou último:
nil or "default" -- "default"
false or nil or 42 -- 42
1 or 2 -- 1
-- not: retorna boolean:
not nil -- true
not false -- true
not 0 -- false! (0 é true)
not "" -- false! ("" é true)
not {} -- false! ({} é true)
-- Curto-circuito:
-- and para no 1º falso
-- or para no 1º verdadeiroand e or em Lua NÃO retornam boolean — retornam o operando: and retorna o primeiro falso ou o último; or retorna o primeiro verdadeiro ou o último. Ambos fazem curto-circuito. not é o único que retorna sempre true/false.
Padrão ternário (and/or)
-- Ternário idiomático:
local status = ativo and "online" or "offline"
local nome = input ~= "" and input or "Anónimo"
-- Equivalente a:
-- status = ativo ? "online" : "offline"
-- CUIDADO: falha se A é false/nil!
local x = true and false or "ops"
-- x = "ops" (não false!)
-- Solução com table:
local r = (cond and {valorA} or {valorB})[1]
-- Default value (mais seguro):
local timeout = opts.timeout or 30
local host = config.host or "localhost"
-- Execução condicional:
debug and print("modo debug ativo")O padrão cond and A or B simula um ternário — funciona quando A nunca é false/nil. Se A pode ser falso, use o truque da table (cond and {A} or {B})[1]. Para defaults, x = valor or default é o padrão mais comum e seguro (equivalente a ?? de outras linguagens).
Concatenação e length
-- Concatenação (..):
"Olá" .. " " .. "Mundo" -- "Olá Mundo"
"idade: " .. 30 -- "idade: 30"
"x=" .. 3.14 -- "x=3.14"
-- Atenção: números colados
print(10 .. 20) -- "1020" (string!)
print(10 + 20) -- 30 (soma)
-- Length (#):
#"texto" -- 5 (bytes)
#{1, 2, 3} -- 3 (array)
#"" -- 0
-- # com tables (parte sequencial):
local t = {10, 20, 30, nome="x"}
#t -- 3 (só parte array)
-- # NÃO funciona com nils no meio:
local bad = {1, nil, 3}
#bad -- indefinido!O operador .. concatena strings e converte números automaticamente — 10 .. 20 produz "1020". # retorna o comprimento de strings (bytes) e a parte sequencial de tables. Cuidado: # é indefinido em arrays com nil no meio — use table.pack com campo .n para contar com segurança.
Operador # e tables
-- # em strings (bytes):
#"hello" -- 5
#"olá" -- 4 (UTF-8: á = 2 bytes)
-- # em arrays (parte sequencial):
#{10, 20, 30} -- 3
#{} -- 0
-- Append usando #:
local t = {}
t[#t + 1] = "primeiro"
t[#t + 1] = "segundo"
-- PERIGO: # com nils no meio
local bad = {1, 2, nil, 4}
#bad -- pode ser 2 ou 4!
-- Solução: table.pack preserva count
local safe = table.pack(1, nil, 3)
print(safe.n) -- 3 (count real)
-- table.insert é mais seguro:
table.insert(t, "terceiro")#t retorna o "border" da parte sequencial — com nil no meio, o resultado é indefinido. Para append, t[#t+1] = v é idiomático mas table.insert(t, v) é mais seguro. table.pack (5.3+) preserva nils com o campo .n. Nunca confie em # para arrays esparsos.
Tables
Tables como arrays
-- Criação (índice 1-based!):
local frutas = {"maçã", "banana", "uva"}
print(frutas[1]) -- "maçã" (NÃO [0]!)
print(frutas[3]) -- "uva"
print(#frutas) -- 3
-- Construção dinâmica:
local nums = {}
for i = 1, 10 do
nums[#nums + 1] = i * i
end
-- Inserção e remoção:
table.insert(frutas, "manga") -- fim
table.insert(frutas, 1, "kiwi") -- posição
table.remove(frutas, 2) -- remove pos 2
table.remove(frutas) -- remove último
-- Unpack:
local a, b, c = table.unpack({10, 20, 30})Tables em Lua são 1-based — o primeiro elemento é [1], não [0]. #t retorna o comprimento da parte sequencial. table.insert/table.remove manipulam posições; sem posição, operam no fim. table.unpack espalha elementos como argumentos múltiplos. É a convenção mais importante ao vir de outras linguagens.
table.concat e pack
-- concat (juntar com separador):
local palavras = {"Lua", "é", "rápida"}
print(table.concat(palavras, " "))
-- "Lua é rápida"
print(table.concat(palavras, ", ", 1, 2))
-- "Lua, é"
-- Eficiente para strings grandes:
local partes = {}
for i = 1, 1000 do
partes[#partes + 1] = "linha " .. i
end
local texto = table.concat(partes, "\n")
-- table.pack (5.3+, preserva nils):
local t = table.pack(1, nil, 3)
print(t.n) -- 3 (count real!)
local a, b, c = table.unpack(t, 1, t.n)
-- table.move (copiar ranges, 5.3+):
local arr = {1, 2, 3, 4, 5}
table.move(arr, 2, 4, 1) -- shift lefttable.concat é a forma eficiente de construir strings grandes — concatenação repetida com .. é O(n²). table.pack preserva nils com campo .n ({...} perde nils trailing). table.move (5.3+) copia ranges eficientemente. Use concat sempre que construir output com muitas partes.
Filter, map e reduce
-- Lua não tem built-in, implementar:
-- Filter:
local function filter(t, pred)
local result = {}
for _, v in ipairs(t) do
if pred(v) then
result[#result + 1] = v
end
end
return result
end
-- Map:
local function map(t, fn)
local result = {}
for i, v in ipairs(t) do
result[i] = fn(v)
end
return result
end
-- Reduce:
local function reduce(t, fn, init)
local acc = init
for _, v in ipairs(t) do
acc = fn(acc, v)
end
return acc
end
local soma = reduce({1,2,3,4}, function(a, b)
return a + b
end, 0) -- 10Lua não tem map/filter/reduce built-in — implementam-se como funções utilitárias com loops. O padrão #result+1 para append é idiomático. Bibliotecas como Moses ou lua-functional adicionam estas funções com chaining. A construção manual é simples e performante — muitas vezes preferível em Lua pela clareza.
Tables como dicionários
-- Criação com chaves:
local pessoa = {
nome = "Ana",
idade = 30,
["e-mail"] = "ana@mail.com",
}
-- Acesso:
print(pessoa.nome) -- dot notation
print(pessoa["e-mail"]) -- bracket (chaves especiais)
pessoa.cidade = "Lisboa" -- adicionar
pessoa.idade = nil -- "remover"
-- Iteração (sem ordem garantida):
for chave, valor in pairs(pessoa) do
print(chave .. ": " .. tostring(valor))
end
-- Verificar existência:
if pessoa.email ~= nil then
print("tem email")
end
-- Qualquer valor exceto nil como chave:
local t = {[true] = "yes", [42] = "answer"}Tables como dicionários usam qualquer valor exceto nil como chave. Dot notation (t.campo) só funciona para identificadores válidos — para chaves especiais use bracket (t["e-mail"]). pairs() itera todos os pares sem ordem garantida. Definir campo como nil "remove" a entrada.
Sets com tables
-- Set: table com valores true
local function novo_set(lista)
local set = {}
for _, v in ipairs(lista) do
set[v] = true
end
return set
end
local a = novo_set({1, 2, 3, 4, 5})
local b = novo_set({3, 4, 5, 6, 7})
-- Pertença O(1):
if a[3] then print("3 está em a") end
-- União:
local uniao = {}
for k in pairs(a) do uniao[k] = true end
for k in pairs(b) do uniao[k] = true end
-- Interseção:
local inter = {}
for k in pairs(a) do
if b[k] then inter[k] = true end
end
-- Remover duplicados:
local seen = {}
local unicos = {}
for _, v in ipairs(lista) do
if not seen[v] then
seen[v] = true
unicos[#unicos + 1] = v
end
endSets em Lua são tables com valores true — verificação de pertença (set[x]) é O(1) vs O(n) de procurar num array. Operações de conjunto são loops sobre pairs(). O padrão "seen" remove duplicados preservando ordem. Ideais para tracking de estado visitado (grafos, BFS) e verificação frequente de pertença.
Tables aninhadas
-- Estruturas JSON-like:
local dados = {
users = {
{ nome = "Ana", idade = 30 },
{ nome = "Bruno", idade = 25 },
},
meta = { total = 2, pagina = 1 },
}
print(dados.users[1].nome) -- "Ana"
print(dados.meta.total) -- 2
-- Construção dinâmica:
local arvore = {}
arvore.filhos = {}
arvore.filhos[1] = {valor = "A", filhos = {}}
-- Acesso seguro (evitar nil error):
local nome = dados.users[3]
and dados.users[3].nome
or "desconhecido"
-- Tables mistas (array + hash):
local config = {
"localhost", -- [1]
8080, -- [2]
debug = true,
name = "server",
}Tables são a ÚNICA estrutura de dados em Lua — servem como array, dict, objeto, módulo e registo. Aninhamento cria estruturas acedidas com notação encadeada. Cuidado: aceder campo de nil causa erro — use and para acesso seguro em caminhos opcionais. Tables mistas combinam parte array e hash na mesma table.
Weak tables e caches
-- Weak table (não impede GC):
local cache = setmetatable({}, {
__mode = "v" -- values fracos
})
-- Memoization com weak cache:
local memo = setmetatable({}, {__mode = "kv"})
local function fib(n)
if memo[n] then return memo[n] end
local result
if n < 2 then result = n
else result = fib(n-1) + fib(n-2) end
memo[n] = result
return result
end
-- __mode options:
-- "k" = chaves fracas
-- "v" = valores fracos
-- "kv" = ambos fracos
-- Quando o objeto original é libertado,
-- a entrada desaparece automaticamenteWeak tables permitem que o garbage collector remova entradas quando chaves/valores não são referenciados noutro lugar. __mode = "v" torna valores fracos, "k" chaves, "kv" ambos. Permitem caches sem memory leaks — quando o objeto é libertado, a entrada desaparece. Essenciais para memoization e object pools.
table.sort
-- Sort ascendente (in-place):
local nums = {5, 3, 8, 1, 9}
table.sort(nums) -- {1, 3, 5, 8, 9}
-- Sort com comparador:
table.sort(nums, function(a, b)
return a > b -- descendente
end)
-- Sort de records:
local pessoas = {
{nome = "Ana", idade = 30},
{nome = "Bruno", idade = 25},
{nome = "Carla", idade = 28},
}
table.sort(pessoas, function(a, b)
return a.idade < b.idade
end)
-- Stable sort (adicionar índice):
for i, v in ipairs(pessoas) do
v._idx = i
end
table.sort(pessoas, function(a, b)
if a.idade == b.idade then
return a._idx < b._idx
end
return a.idade < b.idade
end)table.sort é in-place e usa quicksort — o comparador deve retornar true se a vem antes de b. A estabilidade NÃO é garantida — para stable sort, adicione índice original como desempate. O comparador deve ser consistente (ordem total). Não modifique a table durante o sort.
Swap-remove e performance
-- Remoção O(1) (sem preservar ordem):
local function remove_fast(t, i)
t[i] = t[#t] -- último para posição
t[#t] = nil -- remove último
end
-- vs table.remove (O(n), preserva ordem):
table.remove(t, i) -- shift de todos
-- Pre-alocação para performance:
local buffer = {}
for i = 1, 10000 do
buffer[i] = 0 -- pré-alocar
end
-- Evitar # em loop quente:
local len = #t -- calcular 1 vez
for i = 1, len do
processar(t[i])
end
-- ipairs vs for numérico:
-- for i=1,#t é mais rápido que ipairs
for i = 1, #t do
local v = t[i]
endSwap-remove substitui o elemento pelo último e remove — O(1) quando a ordem não importa (essencial em game loops). table.remove é O(n) pois faz shift. Em loops quentes, cache #t fora do loop. for i=1,#t é ligeiramente mais rápido que ipairs em Lua 5.3+. Pré-alocação evita rehashing.
Metatables e OOP
Metatables básico
-- Metatable define comportamento de tables:
local mt = {}
local t = setmetatable({}, mt)
-- setmetatable retorna a própria table:
local obj = setmetatable({}, {})
-- getmetatable:
local mt2 = getmetatable(t)
-- __index (fallback de leitura):
local defaults = {cor = "branco", tamanho = 10}
local o = setmetatable({}, {__index = defaults})
print(o.cor) -- "branco" (de defaults)
o.cor = "azul" -- cria campo próprio
print(o.cor) -- "azul" (próprio)
-- __newindex (interceptar escrita):
local strict = setmetatable({}, {
__newindex = function(t, k, v)
error("Campo não permitido: " .. k)
end
})Metatables são o mecanismo de extensão de Lua — definem como tables se comportam em operações. __index é consultado quando um campo NÃO existe — pode ser table (delegation) ou função (computed). __newindex intercepta escrita de campos novos. setmetatable retorna a table, permitindo chaining.
Classes padrão
-- Padrão universal de classes em Lua:
local Animal = {}
Animal.__index = Animal
function Animal.new(nome, som)
local self = setmetatable({}, Animal)
self.nome = nome
self.som = som or "..."
return self
end
function Animal:falar()
return self.nome .. ": " .. self.som .. "!"
end
function Animal:__tostring()
return "Animal(" .. self.nome .. ")"
end
-- Uso:
local rex = Animal.new("Rex", "Au")
print(rex:falar()) -- "Rex: Au!"
print(rex) -- "Animal(Rex)"
-- rex:falar() == rex.falar(rex)O padrão de classes usa __index apontando para a própria table: campos inexistentes no objeto são procurados na classe. Métodos usam : que passa self automaticamente. Não há classes nativas em Lua — este padrão prototype-based com metatables é a convenção universal. Cada objeto é uma table com metatable.
Mixin e composição
-- Mixin: copiar métodos para classe
local Serializable = {}
function Serializable:to_json()
local parts = {}
for k, v in pairs(self) do
if type(v) ~= "function" then
parts[#parts + 1] = string.format(
'"%s":"%s"', k, tostring(v))
end
end
return "{" .. table.concat(parts, ",") .. "}"
end
local Loggable = {}
function Loggable:log(msg)
print("[" .. tostring(self) .. "] " .. msg)
end
-- Aplicar mixins:
local User = {}
User.__index = User
for k, v in pairs(Serializable) do User[k] = v end
for k, v in pairs(Loggable) do User[k] = v end
function User.new(nome)
return setmetatable({nome = nome}, User)
endComposição via mixin copia métodos de módulos para classes — alternativa à herança múltipla sem complexidade de cadeia __index. Vantagens: sem acoplamento hierárquico, sem diamond problem, composição seletiva. Desvantagem: conflitos de nomes são silenciosos (último a copiar ganha). Prefira composição quando comportamentos são independentes.
__index e delegation
-- __index como table (prototype):
local Animal = {som = "...", patas = 4}
local cao = setmetatable({}, {__index = Animal})
print(cao.som) -- "..." (herdado)
print(cao.patas) -- 4 (herdado)
cao.som = "Au" -- campo próprio
print(cao.som) -- "Au" (override)
-- __index como função (computed):
local proxy = setmetatable({}, {
__index = function(t, key)
return "campo '" .. key .. "' não existe"
end
})
print(proxy.xyz) -- "campo 'xyz' não existe"
-- Cadeia de prototypes:
local A = {nivel = "A"}
local B = setmetatable({}, {__index = A})
local C = setmetatable({}, {__index = B})
print(C.nivel) -- "A" (C -> B -> A)__index como table cria delegation/prototype — a lookup segue a cadeia até encontrar. Como função, permite computed properties (valores calculados on-demand). A cadeia pode ter múltiplos níveis (C → B → A). É o mecanismo fundamental para OOP, módulos com defaults e configuração com fallback em Lua.
Herança
-- Classe base:
local Animal = {}
Animal.__index = Animal
function Animal.new(nome)
return setmetatable({nome = nome}, Animal)
end
function Animal:falar()
return self.nome .. " faz ..."
end
-- Classe derivada:
local Cao = setmetatable({}, {__index = Animal})
Cao.__index = Cao
function Cao.new(nome, raca)
local self = Animal.new(nome)
setmetatable(self, Cao)
self.raca = raca
return self
end
function Cao:falar() -- override
return self.nome .. ": Au au!"
end
-- Cadeia: Cao -> Animal (via __index)
local rex = Cao.new("Rex", "Labrador")
print(rex:falar()) -- "Rex: Au au!"Herança é uma cadeia de __index: Cao.__index = Cao e a metatable de Cao tem __index = Animal. Lookup: rex → Cao → Animal. Override é simples: definir método em Cao esconde o de Animal. Para chamar a "superclasse", use Animal.falar(self) explicitamente — não há super nativo.
Metamethods aritméticos
-- Vector com metamethods:
local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({x = x, y = y}, Vector)
end
function Vector.__add(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
function Vector.__mul(v, scalar)
return Vector.new(v.x * scalar, v.y * scalar)
end
function Vector.__unm(v)
return Vector.new(-v.x, -v.y)
end
function Vector.__eq(a, b)
return a.x == b.x and a.y == b.y
end
local v = Vector.new(1, 2) + Vector.new(3, 4)
print(v.x, v.y) -- 4, 6Metamethods aritméticos (__add, __sub, __mul, __div, __mod, __pow, __unm) permitem que objetos se comportem como tipos nativos. __eq, __lt, __le para comparações. Essenciais para tipos matemáticos (Vector, Matrix, Complex) e DSLs transparentes.
Encapsulamento com closures
-- Estado privado com closures:
local function nova_conta(saldo_inicial)
local saldo = saldo_inicial -- privado!
local historico = {} -- privado!
return {
depositar = function(v)
saldo = saldo + v
historico[#historico + 1] = "+" .. v
end,
sacar = function(v)
if v > saldo then
error("Saldo insuficiente")
end
saldo = saldo - v
end,
get_saldo = function()
return saldo
end,
}
end
local conta = nova_conta(100)
conta.depositar(50)
print(conta.get_saldo()) -- 150
-- conta.saldo -- nil! (inacessível)Lua não tem private/protected — encapsulamento é conseguido com closures: variáveis locais são capturadas pelas funções retornadas mas inacessíveis externamente. Este padrão "object as closure" é mais seguro que metatables (onde campos são sempre acessíveis via rawget). Desvantagem: mais memória por objeto.
__tostring e __call
-- __tostring (usado por print):
local Point = {}
Point.__index = Point
function Point.new(x, y)
return setmetatable({x = x, y = y}, Point)
end
function Point.__tostring(p)
return string.format("(%g, %g)", p.x, p.y)
end
local p = Point.new(3, 4)
print(p) -- "(3, 4)"
-- __call (objeto como função):
function Point.__call(p, factor)
return Point.new(p.x * factor, p.y * factor)
end
local p2 = p(2) -- chama como função!
print(p2) -- "(6, 8)"
-- __len (operador #):
function Point.__len(p)
return math.sqrt(p.x^2 + p.y^2)
end
print(#p) -- 5.0 (magnitude)__tostring é invocado por print() e tostring() — permite representação legível de objetos. __call permite usar objetos como funções (obj()) — útil para builders e DSLs. __len intercepta o operador #. __concat intercepta ... Estes metamethods tornam objetos transparentes.
__call como constructor
-- Classe como callable:
local Point = {}
Point.__index = Point
-- Metatable da CLASSE (não do objeto!):
setmetatable(Point, {
__call = function(cls, x, y)
return setmetatable({x = x, y = y}, cls)
end
})
-- Point() funciona como constructor:
local p = Point(3, 4) -- sem .new()!
print(p.x, p.y) -- 3, 4
-- Com validação:
local Email = {}
Email.__index = Email
setmetatable(Email, {
__call = function(cls, addr)
assert(addr:match("@"), "Email inválido")
return setmetatable({addr = addr}, cls)
end
})
local e = Email("ana@mail.com") -- OK__call na metatable da CLASSE permite usar a classe como função — Point(3, 4) em vez de Point.new(3, 4). O primeiro argumento é a própria classe (cls), seguido dos args. Cria APIs mais limpas e idiomáticas. É o padrão preferido em bibliotecas modernas de Lua. Combinado com validação, o constructor torna-se seguro.