00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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’.