Moduli e Packages in Lua
I moduli sono il meccanismo principale per organizzare e riutilizzare il codice in Lua. Un modulo e’ semplicemente un file Lua che restituisce una tabella contenente funzioni, costanti e dati. La funzione require() carica e importa i moduli, mentre il sistema dei package gestisce i percorsi di ricerca e la cache.
La Funzione require()
La funzione require() e’ il punto di ingresso per caricare un modulo. Accetta il nome del modulo come stringa (senza estensione .lua) e restituisce il valore ritornato dal modulo stesso.
-- Caricare un modulo
local json = require("json")
local utils = require("utils")
-- I moduli in sottocartelle usano il punto come separatore
local db = require("app.database")
-- Cerca il file: app/database.lua
require() esegue il file del modulo e ne restituisce il valore di ritorno. Se il modulo e’ gia’ stato caricato, restituisce il risultato dalla cache senza rieseguire il file.
Creare un Modulo
Il modo standard per creare un modulo in Lua e’ definire una tabella locale, aggiungere funzioni e dati, e restituirla alla fine del file.
-- File: matematica.lua
local M = {}
M.PI = 3.14159265358979
M.E = 2.71828182845905
function M.somma(a, b)
return a + b
end
function M.sottrai(a, b)
return a - b
end
function M.moltiplica(a, b)
return a * b
end
function M.dividi(a, b)
if b == 0 then
return nil, "Divisione per zero"
end
return a / b
end
function M.fattoriale(n)
if n <= 1 then return 1 end
return n * M.fattoriale(n - 1)
end
return M
Per utilizzare il modulo:
local mat = require("matematica")
print(mat.PI) -- 3.14159265358979
print(mat.somma(10, 5)) -- 15
print(mat.fattoriale(5)) -- 120
local risultato, err = mat.dividi(10, 0)
if not risultato then
print("Errore: " .. err)
end
Funzioni Private nel Modulo
Le funzioni dichiarate come local nel file del modulo e non inserite nella tabella di ritorno restano private e inaccessibili dall’esterno.
-- File: validatore.lua
local M = {}
-- Funzione privata: non accessibile dall'esterno
local function contaCaratteri(stringa, carattere)
local conteggio = 0
for i = 1, #stringa do
if stringa:sub(i, i) == carattere then
conteggio = conteggio + 1
end
end
return conteggio
end
-- Funzione privata di utilita'
local function nonVuoto(stringa)
return stringa ~= nil and stringa ~= ""
end
-- Funzioni pubbliche
function M.validaEmail(email)
if not nonVuoto(email) then
return false, "Email vuota"
end
if contaCaratteri(email, "@") ~= 1 then
return false, "Email deve contenere esattamente una @"
end
if not email:match("^[%w%.]+@[%w%.]+%.%w+$") then
return false, "Formato email non valido"
end
return true
end
function M.validaPassword(password)
if not nonVuoto(password) then
return false, "Password vuota"
end
if #password < 8 then
return false, "Password troppo corta (minimo 8 caratteri)"
end
return true
end
return M
Il Percorso di Ricerca: package.path
Quando si chiama require("modulo"), Lua cerca il file seguendo i pattern definiti in package.path. Ogni pattern usa il carattere ? come segnaposto per il nome del modulo.
-- Visualizzare il percorso di ricerca attuale
print(package.path)
-- Tipico output:
-- ./?.lua;./?/init.lua;/usr/local/share/lua/5.4/?.lua;...
-- Aggiungere un percorso personalizzato
package.path = package.path .. ";/mio/progetto/?.lua"
package.path = package.path .. ";/mio/progetto/?/init.lua"
-- Ora require("miomodulo") cerchera' anche in:
-- /mio/progetto/miomodulo.lua
-- /mio/progetto/miomodulo/init.lua
Per i moduli scritti in C, esiste package.cpath che funziona allo stesso modo ma cerca file .so (Linux) o .dll (Windows).
print(package.cpath)
-- Tipico output:
-- ./?.so;/usr/local/lib/lua/5.4/?.so;...
La Cache: package.loaded
Lua mantiene una cache dei moduli caricati in package.loaded. Quando require() viene chiamata, controlla prima questa cache. Se il modulo e’ presente, lo restituisce senza rieseguire il file.
-- Il modulo viene eseguito solo la prima volta
local a = require("miomodulo") -- esegue il file
local b = require("miomodulo") -- restituisce dalla cache
print(a == b) -- true (stessa tabella)
-- Per forzare il ricaricamento di un modulo
package.loaded["miomodulo"] = nil
local c = require("miomodulo") -- riesegue il file
print(a == c) -- false (nuova tabella)
Questa cache e’ utile anche per verificare se un modulo e’ stato caricato:
if package.loaded["socket"] then
print("Il modulo socket e' disponibile")
end
Organizzare i Moduli in Package
Un package e’ una collezione di moduli organizzati in una directory. Il file init.lua funge da punto di ingresso del package.
mioprogetto/
main.lua
app/
init.lua -- require("app")
config.lua -- require("app.config")
modelli/
init.lua -- require("app.modelli")
utente.lua -- require("app.modelli.utente")
prodotto.lua -- require("app.modelli.prodotto")
utils/
init.lua -- require("app.utils")
stringhe.lua -- require("app.utils.stringhe")
-- File: app/init.lua
local M = {}
M.config = require("app.config")
M.modelli = require("app.modelli")
M.utils = require("app.utils")
M.versione = "1.0.0"
return M
-- File: app/modelli/init.lua
local M = {}
M.Utente = require("app.modelli.utente")
M.Prodotto = require("app.modelli.prodotto")
return M
-- File: main.lua
local app = require("app")
print(app.versione) -- 1.0.0
local utente = app.modelli.Utente.new("Mario")
LuaRocks: Il Gestore di Pacchetti
LuaRocks e’ il gestore di pacchetti standard per Lua, simile a pip per Python o npm per Node.js. Permette di installare, gestire e distribuire moduli Lua.
# Installare LuaRocks (su sistemi basati su Debian)
sudo apt install luarocks
# Installare un pacchetto
luarocks install luasocket
luarocks install lua-cjson
luarocks install penlight
# Cercare un pacchetto
luarocks search json
# Elencare i pacchetti installati
luarocks list
# Rimuovere un pacchetto
luarocks remove luasocket
Dopo l’installazione tramite LuaRocks, i moduli possono essere importati normalmente con require().
-- Dopo: luarocks install luasocket
local socket = require("socket")
local http = require("socket.http")
-- Dopo: luarocks install lua-cjson
local cjson = require("cjson")
local dati = cjson.decode('{"nome": "Mario"}')
print(dati.nome) -- Mario
Esempio Pratico: Modulo di Logging
Ecco un esempio completo di un modulo di logging ben strutturato che dimostra le best practices.
-- File: logger.lua
local M = {}
-- Costanti private
local LIVELLI = {
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4
}
local NOMI_LIVELLI = { "DEBUG", "INFO", "WARN", "ERROR" }
-- Stato privato del modulo
local livello_corrente = LIVELLI.INFO
local destinazione = io.stdout
-- Funzione privata di formattazione
local function formatta(livello, messaggio)
local data = os.date("%Y-%m-%d %H:%M:%S")
return string.format("[%s] [%s] %s", data, NOMI_LIVELLI[livello], messaggio)
end
-- Funzioni pubbliche
function M.setLivello(livello)
assert(LIVELLI[livello], "Livello non valido: " .. tostring(livello))
livello_corrente = LIVELLI[livello]
end
function M.setDestinazione(file)
if type(file) == "string" then
local f, err = io.open(file, "a")
if not f then
error("Impossibile aprire il file di log: " .. err)
end
destinazione = f
else
destinazione = file
end
end
function M.debug(messaggio)
if livello_corrente <= LIVELLI.DEBUG then
destinazione:write(formatta(LIVELLI.DEBUG, messaggio) .. "\n")
end
end
function M.info(messaggio)
if livello_corrente <= LIVELLI.INFO then
destinazione:write(formatta(LIVELLI.INFO, messaggio) .. "\n")
end
end
function M.warn(messaggio)
if livello_corrente <= LIVELLI.WARN then
destinazione:write(formatta(LIVELLI.WARN, messaggio) .. "\n")
end
end
function M.errore(messaggio)
if livello_corrente <= LIVELLI.ERROR then
destinazione:write(formatta(LIVELLI.ERROR, messaggio) .. "\n")
end
end
-- Esponiamo le costanti dei livelli
M.LIVELLI = LIVELLI
return M
-- Utilizzo del modulo logger
local log = require("logger")
log.setLivello("DEBUG")
log.debug("Avvio applicazione")
log.info("Server in ascolto sulla porta 8080")
log.warn("Memoria quasi esaurita")
log.errore("Connessione al database persa")
-- Output:
-- [2026-02-08 10:30:00] [DEBUG] Avvio applicazione
-- [2026-02-08 10:30:00] [INFO] Server in ascolto sulla porta 8080
-- [2026-02-08 10:30:00] [WARN] Memoria quasi esaurita
-- [2026-02-08 10:30:00] [ERROR] Connessione al database persa
Best Practices per i Moduli
- Restituisci sempre una tabella: il modulo deve restituire una singola tabella con tutte le funzioni pubbliche.
- Usa variabili locali: tutto cio’ che non deve essere esposto deve essere dichiarato
local. - Non inquinare lo scope globale: evita di creare variabili globali nei moduli.
- Documenta l’interfaccia pubblica: indica chiaramente quali funzioni e costanti sono disponibili.
- Gestisci le dipendenze: carica le dipendenze all’inizio del file con
require(). - Evita effetti collaterali: il caricamento del modulo non dovrebbe modificare lo stato globale.
- Usa nomi significativi: il nome del file del modulo deve riflettere il suo contenuto.
Conclusione
Il sistema dei moduli di Lua, pur essendo semplice, e’ estremamente efficace per organizzare progetti di qualsiasi dimensione. La combinazione di require(), la tabella package.loaded e una buona struttura delle directory permette di creare codebase manutenibili e modulari. LuaRocks completa l’ecosistema offrendo accesso a migliaia di moduli della comunita’.