1. Introduction à Lua 5.5
Lua 5.5 est la dernière évolution majeure du langage Lua. Elle introduit des mécanismes puissants de gestion des ressources et de constance, tout en conservant la philosophie de simplicité et de légèreté qui fait la renommée de Lua.
Historique et contexte
Lua 5.5 a été annoncé par l'équipe de la PUC-Rio comme la suite naturelle de Lua 5.4 (sorti en 2020). Les principaux objectifs de cette nouvelle version sont : améliorer la gestion automatique des ressources grâce aux variables <close>, renforcer la sécurité du code avec <const>, et affiner les performances du VM.
-- Comparaison rapide entre Lua 5.4 et Lua 5.5
-- Lua 5.4 : pas d'attributs de variables
local x = 10 -- x peut être réassignée
x = 20 -- pas d'erreur
-- Lua 5.5 : attributs de variables
local <const> PI = 3.14159 -- PI est constante
-- PI = 3.0 → erreur : assignment to a read-only variable
Ce qui change par rapport à Lua 5.4
- Attributs de variables :
<const>pour les constantes,<close>pour les variables à fermeture automatique. - Variables to-be-closed : nettoyage automatique des ressources (fichiers, connexions, verrous) en fin de bloc, similaire au
try/finallyoudefer. - Améliorations aux boucles : nouveaux patrons d'itération et meilleur comportement des labels.
- Fonctions améliorées : attributs sur les fonctions, meilleurs messages d'erreur.
- Performance du VM : optimisations bytecode, meilleure collecte d'ordures.
Rétrocompatibilité
Lua 5.5 reste largement rétrocompatible avec Lua 5.4. La plupart des scripts écrits en Lua 5.4 fonctionneront sans modification. Les changements incompatibles sont minimes et documentés dans les notes de la release.
-- Un script Lua 5.4 fonctionne en Lua 5.5
local tab = {1, 2, 3, 4, 5}
for i, v in ipairs(tab) do
print(i, v)
end
-- Les nouvelles fonctionnalités sont optionnelles
-- Vous pouvez les adopter progressivement
Installation
Pour installer Lua 5.5, vous pouvez compiler à partir des sources officiels ou utiliser un gestionnaire de paquets :
-- Compilation depuis les sources
-- git clone https://github.com/lua/lua.git
-- cd lua && git checkout v5.5.0
-- make linux (ou make macosx, make mingw...)
-- Vérification de la version
-- lua -v
-- Lua 5.5.0 Copyright (C) 1994-2026 Lua.org, PUC-Rio
-- Vérifier la version dans le REPL
print(_VERSION) --> "Lua 5.5"
Aperçu du cours
Ce cours couvrira en profondeur chaque nouveauté de Lua 5.5. Nous explorerons les attributs de variables, les variables to-be-closed, les améliorations aux boucles et fonctions, la gestion des erreurs, les générateurs, l'interopérabilité C, les optimisations de performance et les patterns avancés.
-- Un aperçu de ce que nous allons apprendre :
local <const> VERSION = "5.5"
local <close> fichier = io.open("donnees.txt", "r")
if fichier then
local contenu = fichier:read("*a")
print("Lua " .. VERSION .. " : " .. #contenu .. " octets")
-- Le fichier sera automatiquement fermé en fin de bloc
end
Récapitulatif
- Lua 5.5 introduit
<const>,<close>et les variables to-be-closed. - Rétrocompatible avec Lua 5.4 pour la plupart des scripts.
- Objectif principal : meilleure gestion des ressources et sécurité.
- Installation depuis les sources ou via LuaRocks.
- Chaque nouveauté est optionnelle, adoption progressive possible.
2. Les attributs de variables (<const> et <close>)
Lua 5.5 introduit les attributs de variables, un mécanisme qui permet de qualifier le comportement d'une variable locale lors de sa déclaration. Les deux attributs principaux sont <const> et <close>.
L'attribut <const>
Une variable déclarée avec <const> ne peut plus être réassignée après son initialisation. Toute tentative de modification provoque une erreur à l'exécution.
-- Déclaration d'une constante
local <const> MAX_UTILISATEURS = 100
local <const> NOM_APPLICATION = "MonApp"
print(MAX_UTILISATEURS) --> 100
print(NOM_APPLICATION) --> "MonApp"
-- Tentative de réassignation → ERREUR
-- MAX_UTILISATEURS = 200
-- Erreur : attempt to assign to a read-only variable
<const> sur les fonctions
L'attribut <const> peut également être appliqué aux fonctions locales, empêchant leur réassignation.
-- Fonction constante
local <const> function calculerTVA(montant)
return montant * 0.20
end
print(calculerTVA(100)) --> 20
-- On ne peut pas remplacer la fonction
-- calculerTVA = function(m) return m * 0.10 end
-- Erreur : attempt to assign to a read-only variable
Tables constantes vs référence constante
Un point important : <const> rend la variable constante, pas la table elle-même. On peut modifier le contenu d'une table constante, mais pas réassigner la variable.
-- Table constante (la référence ne peut changer)
local <const> config = {
port = 8080,
host = "localhost",
debug = true
}
-- Modifier un champ : AUTORISÉ
config.debug = false
print(config.debug) --> false
-- Ajouter un champ : AUTORISÉ
config.version = "2.0"
-- Réassigner la variable : INTERDIT
-- config = {}
-- Erreur : attempt to assign to a read-only variable
-- Pour une truly immutable table, utilisez un métaméthode
local <const> donnees = setmetatable({
nom = "Alice",
age = 30
}, {
__newindex = function()
error("Table immutable !")
end
})
-- donnees.nom = "Bob" → erreur via __newindex
L'attribut <close> (aperçu)
L'attribut <close> marque une variable pour être automatiquement « fermée » lorsque son bloc se termine. Nous verrons ce mécanisme en détail dans la leçon 3.
-- Aperçu de <close>
do
local <close> fichier = io.open("data.txt", "r")
if fichier then
local donnees = fichier:read("*a")
print(donnees)
end
-- fichier automatiquement fermé ici !
end
Ordre des attributs et syntaxe
La syntaxe correcte place l'attribut entre local et le nom de la variable. On ne peut pas combiner <const> et <close> sur la même variable.
-- Syntaxe correcte
local <const> x = 10 -- correct
local <close> f = io.open("f.txt", "r") -- correct
-- Syntaxe incorrecte
-- local <const, close> y = 42 → erreur de syntaxe
-- Const avec assignation multiple
local <const> A, B, C = 1, 2, 3
print(A, B, C) --> 1 2 3
-- A = 5 → erreur
Cas d'utilisation de <const>
-- Constantes de configuration
local <const> TAILLE_PAGE = 20
local <const> SEPARATEUR = ";"
local <const> CODES_ERREUR = {
INVALIDE = 400,
NON_TROUVE = 404,
SERVEUR = 500
}
-- Les constantes évitent les bugs d'assignation accidentelle
function paginer(resultats, page)
local debut = (page - 1) * TAILLE_PAGE + 1
local fin = math.min(page * TAILLE_PAGE, #resultats)
local pageResultats = {}
for i = debut, fin do
table.insert(pageResultats, resultats[i])
end
return pageResultats
end
Récapitulatif
<const>empêche la réassignation d'une variable locale.<close>active le nettoyage automatique en fin de bloc (détail leçon 3).<const>sur une table interdit la réassignation, pas la modification du contenu.- On ne peut pas combiner
<const>et<close>sur la même variable. - Les fonctions locales peuvent aussi être
<const>.
3. Variables to-be-closed
Les variables to-be-closed (à fermer) sont la nouveauté la plus attendue de Lua 5.5. Elles permettent le nettoyage automatique des ressources à la fin d'un bloc, offrant un mécanisme similaire au defer de Go ou au try/finally d'autres langages.
Le problème sans to-be-closed
En Lua 5.4, la fermeture manuelle des ressources nécessite une gestion rigoureuse, ce qui peut conduire à des fuites si une erreur survient.
-- Problème : fuite de ressources si error()
local f = io.open("data.txt", "r")
if f then
local donnees = f:read("*a")
-- Si une erreur survient ici, f ne sera jamais fermée !
traiter(donnees) -- ← peut causer une erreur
f:close() -- ← pourrait ne jamais être atteint
end
La solution avec <close>
L'attribut <close> garantit que la méthode __close sera appelée quand le bloc se termine, que ce soit normalement ou par erreur.
-- Solution : le bloc do...end avec <close>
do
local <close> f = io.open("data.txt", "r")
if f then
local donnees = f:read("*a")
traiter(donnees) -- même si error() est appelé ici...
end
-- f est automatiquement fermée ici !
end
Le protocole __close
Une variable to-be-closed doit avoir une méthode __close (c'est-à-dire un métaméthode). La fonction io.open retourne un fichier qui possède cette méthode.
-- Créer un objet avec __close
local function creerVerrou(nom)
local verrou = {nom = nom, actif = true}
function verrou:__close()
if self.actif then
print("Libération du verrou : " .. self.nom)
self.actif = false
end
end
return verrou
end
do
local <close> monVerrou = creerVerrou("fichier_critique")
print("Travail avec le verrou...")
end
-- Libération du verrou : fichier_critique
Nettoyage avec base de données
-- Simulation de connexion à une base de données
local function ouvrirConnexion(url)
local conn = {
url = url,
ouverte = true,
requetes = 0
}
function conn:executerRequete(sql)
if not self.ouverte then
error("Connexion fermée !")
end
self.requetes = self.requetes + 1
print("Exécution : " .. sql)
return {}
end
function conn:__close()
if self.ouverte then
print("Fermeture de la connexion (requêtes : " .. self.requetes .. ")")
self.ouverte = false
end
end
print("Connexion ouverte à " .. url)
return conn
end
-- Utilisation
do
local <close> db = ouvrirConnexion("mysql://localhost/ma_base")
db:executerRequete("SELECT * FROM users")
db:executerRequete("UPDATE users SET actif=1")
end
-- Connexion ouverte à mysql://localhost/ma_base
-- Exécution : SELECT * FROM users
-- Exécution : UPDATE users SET actif=1
-- Fermeture de la connexion (requêtes : 2)
Comparaison avec d'autres langages
- Go :
defer f.Close()— même concept de nettoyage en fin de fonction/bloc. - Python :
with open(...) as f:— le gestionnaire de contexte est similaire. - C++ :
std::unique_ptret RAII — gestion automatique via destructeurs. - Java :
try (var res = ...) {}— les try-with-resources depuis Java 7.
-- Exemple complet : pipeline de traitement de fichiers
local function traiterFichier(cheminEntree, cheminSortie)
do
local <close> entree = io.open(cheminEntree, "r")
if not entree then
error("Impossible d'ouvrir " .. cheminEntree)
end
local <close> sortie = io.open(cheminSortie, "w")
if not sortie then
error("Impossible de créer " .. cheminSortie)
end
for ligne in entree:lines() do
sortie:write(string.upper(ligne) .. "\n")
end
end
-- Les deux fichiers sont fermés automatiquement !
end
Erreurs et to-be-closed
Les variables to-be-closed sont nettoyées même en cas d'erreur propagée, ce qui garantit la libération des ressources.
local function demoErreur()
do
local <close> res = creerVerrou("temporaire")
print("Début du traitement")
error("Une erreur inattendue !")
-- Le code ci-dessus ne sera jamais exécuté
end
-- Mais __close sera quand même appelé !
end
-- Utilisation de pcall pour capturer l'erreur
local ok, err = pcall(demoErreur)
print(ok, err) --> false "Une erreur inattendue !"
-- La ressource a été libérée malgré l'erreur
Récapitulatif
<close>garantit l'appel à__closeen fin de bloc, même en cas d'erreur.- Les variables to-be-closed doivent être déclarées dans un bloc
do...end. - L'objet doit implémenter la métaméthode
__close. - Similaire à
defer(Go),with(Python), ou les try-with-resources (Java). - Permet de gérer les fichiers, verrous, connexions de base de données de manière sûre.
4. Les améliorations aux boucles
Lua 5.5 apporte des améliorations subtiles mais significatives aux boucles et au contrôle de flux, rendant le code plus prévisible et plus performant.
Améliorations de la boucle for générique
La boucle for générique bénéficie d'optimisations internes pour les itérateurs standards comme pairs et ipairs.
-- ipairs optimisé en Lua 5.5
local fruits = {"pomme", "banane", "cerise", "datte", "elderberry"}
-- ipairs bénéficie d'une meilleure optimisation interne
for i, fruit in ipairs(fruits) do
print(i, fruit)
end
-- pairs a aussi des optimisations pour les tables non mélangées
local config = {timeout = 30, retries = 3, verbose = true}
for cle, valeur in pairs(config) do
print(cle, valeur)
end
Comportement amélioré de break dans les boucles imbriquées
En Lua 5.5, le comportement de break dans les boucles imbriquées est mieux défini et les messages d'erreur sont plus explicites.
-- break sort de la boucle la plus interne
for i = 1, 5 do
for j = 1, 5 do
if i * j > 10 then
print("Sortie à i=" .. i .. " j=" .. j)
break
end
end
end
Améliorations des labels goto
Les labels ::label:: ont un comportement plus strict en Lua 5.5, avec de meilleurs messages d'erreur pour les sauts invalides.
-- goto pour sortir de blocs imbriqués
function rechercher(tableau, critere)
for i, v in ipairs(tableau) do
for j, val in pairs(v) do
if val == critere then
goto trouvé
end
end
end
print("Non trouvé")
return nil
::trouvé::
print("Trouvé !")
return i
end
Itérateurs personnalisés avec coroutine.wrap
La boucle for générique s'intègre parfaitement avec les itérateurs créés via les coroutines.
-- Itérateur de plage avec pas personnalisé
local function plage(debut, fin, pas)
pas = pas or 1
return function()
if debut <= fin then
local courant = debut
debut = debut + pas
return courant
end
end
end
-- Utilisation avec la boucle for
for n in plage(0, 10, 2) do
print(n) -- 0, 2, 4, 6, 8, 10
end
-- Itérateur Fibonacci
local function fibonacci(max)
local a, b = 0, 1
return function()
if a > max then return nil end
local val = a
a, b = b, a + b
return val
end
end
for f in fibonacci(100) do
print(f)
end
-- 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
Numerical for avec pas négatif et bornes dynamiques
-- La boucle for numérique évalue les bornes une seule fois
local t = {10, 20, 30, 40, 50}
for i = 1, #t do
print(i, t[i])
end
-- Parcours inversé efficace
for i = #t, 1, -1 do
print("Inversé:", i, t[i])
end
-- La boucle for est optimisée au bytecode en Lua 5.5
-- Pas besoin de convertir les bornes à chaque itération
Récapitulatif
- La boucle
forgénérique bénéficie d'optimisations pourpairsetipairs. breakfonctionne toujours sur la boucle la plus interne.- Les labels
::label::ont de meilleurs messages d'erreur. - Les itérateurs personnalisés s'intègrent naturellement avec
for...in. - La boucle numérique évalue les bornes une seule fois.
5. Les fonctions améliorées
Lua 5.5 apporte des améliorations significatives aux fonctions, notamment la possibilité de leur appliquer des attributs et des messages d'erreur plus informatifs.
Fonctions avec attribut <const>
Comme les variables, les fonctions locales peuvent être déclarées <const> pour empêcher leur réassignation.
-- Fonction constante
local <const> function calculerMoyenne(notes)
local somme = 0
for _, note in ipairs(notes) do
somme = somme + note
end
return somme / #notes
end
print(calculerMoyenne({12, 15, 18, 14})) --> 14.75
-- On ne peut pas remplacer cette fonction
-- calculerMoyenne = function(t) ... end
-- Erreur : attempt to assign to a read-only variable
Meilleurs messages d'erreur
Lua 5.5 produit des messages d'erreur plus informatifs, incluant souvent le nom de la fonction et le fichier source.
-- En Lua 5.4, l'erreur peut être peu claire
-- En Lua 5.5, le traceback est plus détaillé
local function fonctionA()
return fonctionB()
end
local function fonctionB()
return fonctionC()
end
local function fonctionC()
error("une erreur survenue")
end
local ok, err = pcall(fonctionA)
print(ok) --> false
print(err) -- traceback détaillé avec noms de fonctions et numéros de ligne
Valeurs de retour multiples améliorées
Le comportement des valeurs de retour multiples est plus prévisible dans Lua 5.5, notamment lors de leur utilisation comme arguments.
-- Les valeurs multiples dans les appels de fonctions
local function obtenirCoordonnees()
return 10, 20, 30
end
-- Utilisation directe comme arguments
print(obtenirCoordonnees()) --> 10 20 30
-- Dans une table avec table.pack
local coords = table.pack(obtenirCoordonnees())
print(coords.n) --> 3
-- table.unpack pour déballer
print(table.unpack(coords, 1, coords.n)) --> 10 20 30
Les closures améliorées
Les closures (fermetures) sont plus performantes en Lua 5.5 grâce aux optimisations du VM.
-- Closure avec <const> pour les paramètres configurables
local function creerCompresseur(algorithme, niveaux)
local <const> ALGO = algorithme
local <const> NIVEAUX_MAX = niveaux
return function(donnees)
print("Compression avec " .. ALGO .. " (niveau " .. NIVEAUX_MAX .. ")")
return donnees
end
end
local lz77 = creerCompresseur("LZ77", 9)
lz77("données à compresser")
-- Compression avec LZ77 (niveau 9)
Appels de fonctions et syntaxe
-- Syntaxe d'appel avec options par défaut
local function traiterOptions(opts)
opts = opts or {}
local <const> defauts = {
verbose = false,
timeout = 30,
retries = 3
}
for k, v in pairs(defauts) do
if opts[k] == nil then
opts[k] = v
end
end
return opts
end
local config = traiterOptions({verbose = true})
print(config.verbose, config.timeout, config.retries)
-- true 30 3
Récapitulatif
- Les fonctions locales peuvent porter l'attribut
<const>. - Les messages d'erreur contiennent plus d'informations sur la pile d'appels.
- Les closures sont plus performantes grâce aux optimisations VM.
- Le comportement des valeurs de retour multiples est plus prévisible.
- Les attributs sur les fonctions améliorent la sécurité du code.
6. Tables et nouvelles fonctionnalités
Les tables restent la structure de données centrale de Lua 5.5, mais plusieurs améliorations les rendent plus robustes et plus pratiques.
table.pack et table.unpack améliorés
Les fonctions table.pack et table.unpack ont été affinées pour un meilleur comportement avec les valeurs nil intermédiaires.
-- table.pack préserve le champ .n
local t = table.pack(1, 2, nil, 4, 5)
print(t.n) --> 5 (nombre d'arguments)
print(#t) --> 2 (nil coupe la longueur)
print(t[3]) --> nil
-- table.unpack avec plage précise
local a, b, c = table.unpack(t, 1, t.n)
print(a, b, c) --> 1 2 nil
-- Extraire une sous-plage
local sub = {table.unpack(t, 2, 4)}
print(#sub) --> 2
Le problème du # et les améliorations 5.5
L'opérateur # peut avoir un comportement imprévisible avec les tables contenant des « trous » (nil intermédiaires). Lua 5.5 offre un comportement légèrement plus prévisible.
-- # est bien défini pour les tableaux sans trous
local tab = {1, 2, 3, 4, 5}
print(#tab) --> 5 (garanti)
-- Avec des trous : comportement défini entre deux bornes
local tab2 = {1, 2, nil, 4, 5}
print(#tab2) --> 2 ou 5 (défini comme entre les deux)
-- Utilisez table.pack pour un comptage fiable
local safe = table.pack(1, 2, nil, 4)
print(safe.n) --> 4 (toujours fiable)
table.next amélioré
La fonction table.next (utilisée en interne par pairs) bénéficie d'optimisations en Lua 5.5.
-- table.next pour une itération manuelle
local fruits = {pomme = 5, banane = 3, cerise = 8}
local cle = nil
while true do
cle = table.next(fruits, cle)
if cle == nil then break end
print(cle, fruits[cle])
end
-- En Lua 5.5, table.next peut recevoir un hint
-- pour commencer à un point spécifique dans la table
Les métatables et __close sur les tables
En Lua 5.5, les tables peuvent être utilisées comme variables to-be-closed si elles implémentent __close.
-- Table avec nettoyage automatique
local function creerRessource(nom)
local res = {nom = nom, active = true}
function res:__close()
self.active = false
print("Ressource '" .. self.nom .. "' libérée")
end
return res
end
do
local <close> r1 = creerRessource("base_données")
local <close> r2 = creerRessource("cache")
print("Utilisation des ressources...")
end
-- Ressource 'cache' libérée
-- Ressource 'base_données' libérée
-- (ordre inverse de déclaration)
Table.clone (si disponible)
Certaines implémentations de Lua 5.5 ajoutent table.clone pour copier superficiellement des tables.
-- Table.clone pour une copie superficielle
local original = {nom = "Alice", scores = {10, 20, 30}}
if table.clone then
local copie = table.clone(original)
copie.nom = "Bob"
print(original.nom) --> "Alice" (non modifié)
print(copie.nom) --> "Bob"
-- attention : scores partagé (copie superficielle)
end
-- Copie profonde manuelle si table.clone n'existe pas
local function copieProfonde(t)
if type(t) ~= "table" then return t end
local res = {}
for k, v in pairs(t) do
res[copieProfonde(k)] = copieProfonde(v)
end
return setmetatable(res, getmetatable(t))
end
Récapitulatif
table.pack/table.unpacksont plus fiables avec les nil intermédiaires.#est bien défini pour les tableaux sans trous.table.nextbénéficie d'optimisations internes.- Les tables peuvent implémenter
__closepour le nettoyage automatique. table.clone(si disponible) offre une copie superficielle pratique.
7. Gestion des erreurs avancée
Lua 5.5 améliore la gestion des erreurs avec de meilleurs outils pour le débogage, des messages plus informatifs et des mécanismes de récupération plus robustes.
pcall et xpcall améliorés
Les fonctions pcall et xpcall sont essentielles pour la gestion des erreurs. Lua 5.5 améliore leur comportement et leurs messages.
-- pcall : appel protégé basique
local ok, resultat = pcall(function()
return 2 + 2
end)
print(ok, resultat) --> true 4
-- pcall avec erreur
local ok, err = pcall(function()
error({code = 404, message = "Non trouvé"})
end)
print(ok) --> false
print(err.message) --> "Non trouvé"
xpcall avec handler d'erreur
xpcall accepte un second argument : une fonction de traitement d'erreur qui transforme l'erreur avant de la retourner.
-- xpcall avec un handler d'erreur personnalisé
local function traiterErreur(err)
return {
niveau = "CRITIQUE",
erreur = tostring(err),
horodatage = os.date("%Y-%m-%d %H:%M:%S")
}
end
local ok, err = xpcall(function()
error("panne du disque")
end, traiterErreur)
if not ok then
print(err.niveau) --> "CRITIQUE"
print(err.erreur) --> "panne du disque"
print(err.horodatage) --> "2026-07-22 14:30:00"
end
Traceback détaillé
debug.traceback fournit une pile d'appels complète, utile pour le débogage.
local function niveau3()
error("erreur en niveau 3")
end
local function niveau2()
niveau3()
end
local function niveau1()
niveau2()
end
-- Avec xpcall et debug.traceback
local ok, err = xpcall(niveau1, function(e)
return debug.traceback(e, 2)
end)
print(err)
-- traceback:
-- main.lua:10: in function 'niveau2'
-- main.lua:14: in function 'niveau1'
La bibliothèque debug améliorée
La bibliothèque debug offre plus d'outils pour l'inspection du runtime.
-- debug.getinfo : informations sur une fonction
local function maFonction() end
local info = debug.getinfo(maFonction)
print(info.name) --> "maFonction"
print(info.what) --> "Lua"
print(info.nparams) --> 0
print(info.isvararg) --> false
-- debug.getlocal : inspecter les variables locales
local x = 42
local nom = "test"
print(debug.getlocal(1, 1)) --> "x" 42
print(debug.getlocal(1, 2)) --> "nom" "test"
assert amélioré
La fonction assert génère de meilleurs messages d'erreur en Lua 5.5.
-- assert avec message personnalisé
local function diviser(a, b)
assert(b ~= 0, string.format("Division par zéro : %d / %d", a, b))
return a / b
end
local ok, err = pcall(diviser, 10, 0)
print(err) --> "main.lua:2: Division par zéro : 10 / 0"
-- assert peut valider les types et les structures
local function creerUtilisateur(nom, age)
assert(type(nom) == "string", "nom doit être une chaîne")
assert(type(age) == "number" and age > 0, "age doit être positif")
return {nom = nom, age = age}
end
Gestion d'erreurs avec to-be-closed
-- Combinaison pcall + to-be-closed pour une gestion robuste
local function lireConfig(chemin)
local config = {}
do
local <close> f = io.open(chemin, "r")
if not f then
return nil, "Fichier non trouvé : " .. chemin
end
for ligne in f:lines() do
local cle, valeur = string.match(ligne, "^(%w+)%s*=%s*(.+)$")
if cle then
config[cle] = valeur
end
end
end
return config
end
local cfg, err = lireConfig("config.ini")
if not cfg then
print("Erreur : " .. err)
end
Récapitulatif
pcallcapture les erreurs,xpcallpermet un traitement personnalisé.debug.tracebackfournit une pile d'appels complète.- La bibliothèque
debugoffregetinfo,getlocalpour l'inspection. assertgénère de meilleurs messages d'erreur en Lua 5.5.- Combiner
pcallavec<close>pour une gestion d'erreurs robuste.
8. Les générateurs et itérateurs
Les générateurs et itérateurs sont des patterns puissants en Lua 5.5, combinant coroutines et fonctions pour créer des flux de données paresseux et efficaces.
Créer un générateur avec coroutine
Un générateur est une coroutine qui produit une séquence de valeurs via coroutine.yield.
-- Générateur de nombres premiers
local function premiers(max)
return coroutine.create(function()
for n = 2, max do
local estPremier = true
for i = 2, math.floor(math.sqrt(n)) do
if n % i == 0 then
estPremier = false
break
end
end
if estPremier then
coroutine.yield(n)
end
end
end)
end
-- Utilisation
local gen = premiers(30)
while coroutine.status(gen) ~= "dead" do
local ok, p = coroutine.resume(gen)
if ok and p then print(p) end
end
-- 2, 3, 5, 7, 11, 13, 17, 19, 23, 29
Le protocole d'itérateur en Lua 5.5
Un itérateur en Lua suit le protocole : une fonction qui retourne la valeur suivante, ou nil quand c'est fini.
-- Itérateur simple : parcourir les mots d'une chaîne
local function lireMots(texte)
local pos = 1
return function()
local debut, fin = string.find(texte, "%w+", pos)
if debut then
pos = fin + 1
return string.sub(texte, debut, fin)
end
end
end
for mot in lireMots("Bonjour le monde de Lua 5.5") do
print(mot)
end
Itérateurs à état avec coroutine.wrap
coroutine.wrap retourne une fonction appelable directement, idéal pour les itérateurs à état complexe.
-- Itérateur de lignes CSV avec gestion des guillemets
local function lireCSV(contenu)
return coroutine.wrap(function()
for ligne in contenu:gmatch("[^\n]+") do
local champs = {}
for champ in ligne:gmatch("[^,]+") do
table.insert(champs, champ)
end
coroutine.yield(champs)
end
end)
end
local csv = "nom,age,ville\nAlice,30,Paris\nBob,25,Lyon"
for ligne in lireCSV(csv) do
print(table.concat(ligne, " | "))
end
-- nom | age | ville
-- Alice | 30 | Paris
-- Bob | 25 | Lyon
Itérateur de transformation (map/filter)
-- map : transformer chaque élément
local function map(iterateur, fn)
return function()
local val = iterateur()
if val then return fn(val) end
end
end
-- filter : ne garder que les éléments qui passent le test
local function filter(iterateur, test)
return function()
while true do
local val = iterateur()
if not val then return nil end
if test(val) then return val end
end
end
end
-- Pipeline fonctionnel
local nombres = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
local iter = ipairs(nombres)
local pairs = filter(function(i, v) return v % 2 == 0 end)
-- Utilisation simplifiée
Performance des itérateurs
-- Les closures sont plus rapides en Lua 5.5
-- Évitez les coroutines pour les itérateurs simples
-- Rapide : closure
local function compter(n)
local i = 0
return function()
i = i + 1
if i <= n then return i end
end
end
-- Plus lent : coroutine (utilisez pour la complexité)
local function compterCoro(n)
return coroutine.wrap(function()
for i = 1, n do
coroutine.yield(i)
end
end)
end
Récapitulatif
- Les générateurs utilisent les coroutines pour produire des séquences paresseuses.
- Le protocole d'itérateur : fonction retournant la valeur suivante ou nil.
coroutine.wrapcrée des itérateurs appelables directement.- Les closures sont plus rapides que les coroutines pour les cas simples.
- Les patterns map/filter permettent des pipelines fonctionnels.
9. Interopérabilité et API
L'interopérabilité avec le code C est l'un des points forts de Lua. Lua 5.5 apporte des changements à l'API C pour mieux gérer les userdata et les valeurs utilisateur.
L'API C de Lua 5.5
L'API C permet d'étendre Lua en intégrant des fonctions et des types écrits en C. Lua 5.5 introduit de légères modifications pour plus de sécurité.
/* Exemple d'extension C pour Lua 5.5 */
/* Créer un module C qui ajoute une fonction mathématique */
#include "lua.h"
#include "lauxlib.h"
/* Fonction C appelable depuis Lua */
static int l_factorielle(lua_State *L) {
int n = luaL_checkinteger(L, 1);
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
lua_pushinteger(L, result);
return 1;
}
Userdata en Lua 5.5
Les userdata sont des blocs de mémoire brute gérés par Lua, utilisés pour intégrer des données C. Lua 5.5 améliore leur gestion avec lua_setiuservalue et lua_getiuservalue.
/* En Lua 5.5, lua_setuservalue/lua_getuservalue
sont remplacés par : */
lua_setiuservalue(L, idx, utype);
lua_getiuservalue(L, idx, utype);
/* Les userdata ont maintenant un type numérique
pour distinguer différents kinds */
static int l_creer_connexion(lua_State *L) {
void *ud = lua_newuserdatauv(L, sizeof(Connexion), 1);
luaL_setmetatable(L, "Connexion");
return 1;
}
Embedding Lua 5.5
Intégrer Lua 5.5 dans une application C est similaire aux versions précédentes, avec quelques améliorations.
/* Programme hôte C avec Lua 5.5 */
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
int main(void) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
/* Charger et exécuter un script Lua */
if (luaL_dofile(L, "script.lua") != LUA_OK) {
fprintf(stderr, "Erreur : %s\n", lua_tostring(L, -1));
}
/* Appeler une fonction Lua depuis C */
lua_getglobal(L, "maFonction");
lua_pushinteger(L, 42);
if (lua_pcall(L, 1, 1, 0) == LUA_OK) {
int resultat = lua_tointeger(L, -1);
printf("Résultat : %d\n", resultat);
}
lua_close(L);
return 0;
}
Les métaméthodes pour les userdata
-- Côté Lua : définir les métaméthodes pour un userdata C
local Point = {}
function Point.__add(a, b)
return creerPoint(a.x + b.x, a.y + b.y)
end
function Point.__tostring(p)
return string.format("Point(%d, %d)", p.x, p.y)
end
function Point.__eq(a, b)
return a.x == b.x and a.y == b.y
end
-- Enregistrer la métatable pour un type userdata
luaL_newmetatable(L, "Point")
-- En Lua 5.5, les userdata supportent mieux
-- les uservalue multiples via lua_newuserdatauv
FFI et extensions
Bien que Lua 5.5 n'inclue pas de FFI natif, des bibliothèques comme LuaJIT's FFI ou les extensions C continuent d'être les moyens principaux d'interopérabilité.
-- Charger une bibliothèque C partagée
local socket = require("socket")
-- Utiliser des fonctions C depuis Lua
local tcp = socket.tcp()
tcp:connect("example.com", 80)
tcp:send("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
local response = tcp:receive("*a")
tcp:close()
-- Les bibliothèques C sont transparentes pour l'utilisateur Lua
-- Le même code fonctionne en Lua 5.4 et 5.5
Changements API Lua 5.5
lua_newuserdatauvremplacelua_newuserdataavec support des uservalue multiples.lua_setiuservalue/lua_getiuservalueremplacentlua_setuservalue/lua_getuservalue.- Meilleure gestion de la rétrocompatibilité avec les modules existants.
Récapitulatif
- L'API C de Lua 5.5 est largement compatible avec 5.4.
- Les userdata bénéficient de
lua_newuserdatauvet uservalue multiples. - L'embedding suit le même pattern :
luaL_newstate,luaL_openlibs,luaL_dofile. - Les métaméthodes s'appliquent aux userdata pour un comportement Lua natif.
- Les extensions C (sockets, JSON, etc.) restent les mêmes.
10. Performance et optimisation
Lua 5.5 introduit plusieurs améliorations de performance au niveau du VM, de la collecte de ordures et de la création de tables.
Optimisations du VM
Le VM (Machine Virtuelle) de Lua 5.5 a été optimisé pour réduire le nombre d'opérations courantes.
-- Les boucles numeriques sont plus rapides
-- Lua 5.5 optimise les opérations entières
-- Boucle intensive (mesurez avec os.clock)
local debut = os.clock()
local somme = 0
for i = 1, 10000000 do
somme = somme + i
end
local fin = os.clock()
print(string.format("Temps : %.3fs", fin - debut))
Gestion de la mémoire et garbage collector
Le GC (garbage collector) de Lua 5.5 est plus efficace, avec de meilleurs heuristiques pour le nettoyage incrémental.
-- Configurer le garbage collector
collectgarbage("setpause", 100) -- pause entre les cycles (en %)
collectgarbage("setstepmul", 200) -- taille du pas (en %)
-- Forcer un cycle de GC
collectgarbage("collect")
-- Mesurer l'utilisation mémoire
print(collectgarbage("count")) --> Ko utilisés
-- En Lua 5.5, le GC generational est amélioré
collectgarbage("generational", 20, 10) -- mode generational
Création de tables optimisée
Lua 5.5 optimise la création de tables, notamment les tableaux de taille connue à l'avance.
-- Pré-allouer la taille d'un tableau
local taille = 10000
local t = table.create(taille, 0) -- si disponible en 5.5
-- Alternative : table.pack pour les retours multiples
local resultats = table.pack(maFonction())
-- Éviter les tables temporaires dans les boucles
-- Mauvais : crée une nouvelle table à chaque itération
for i = 1, 1000 do
local temp = {x = i, y = i * 2} -- allocation à chaque tour
end
-- Meilleur : réutiliser une table
local temp = {}
for i = 1, 1000 do
temp.x = i
temp.y = i * 2
end
Strings et interning
Les chaînes en Lua sont internées (partagées en mémoire). Lua 5.5 optimise davantage ce mécanisme.
-- Les chaînes identiques partagent la même référence
local a = "bonjour"
local b = "bonjour"
print(a == b) --> true
print(tostring(a) == tostring(b)) --> true
-- Éviter la création de chaînes inutiles dans les boucles
-- Mauvais : concaténation crée des chaînes temporaires
local resultat = ""
for i = 1, 1000 do
resultat = resultat .. tostring(i) .. ","
end
-- Meilleur : utiliser table.concat
local parties = {}
for i = 1, 1000 do
parties[i] = tostring(i)
end
local resultat = table.concat(parties, ",")
Bytecode et chargement
Lua 5.5 peut charger du bytecode pré-compilé pour un démarrage plus rapide.
-- Pré-compiler un fichier Lua en bytecode
-- luac -o mon_module.luac mon_module.lua
-- Charger le bytecode pré-compilé
local monModule = loadfile("mon_module.luac")()
-- Plus rapide que loadfile("mon_module.lua")
-- string.dump pour sérialiser une fonction en bytecode
local function maFonction() return 42 end
local bytecode = string.dump(maFonction)
print(#bytecode) -- taille du bytecode en octets
-- Recharger depuis le bytecode
local recharge = load(bytecode)
print(recharge()) --> 42
Conseils d'optimisation
- Mesurez toujours avec
os.clock()avant d'optimiser. - Utilisez
localpartout — les accès locaux sont plus rapides. - Préférez
table.concatà la concaténation avec... - Évitez les tables temporaires dans les boucles chaudes.
- Utilisez le bytecode pré-compilé pour les modules fréquemment chargés.
Récapitulatif
- Le VM de Lua 5.5 est optimisé pour les opérations entières courantes.
- Le garbage collector generational est amélioré.
- La création de tables peut être optimisée en pré-allouant.
- Les chaînes sont automatiquement internées.
- Le bytecode pré-compilé accélère le chargement des modules.
11. Patterns et expressions régulières
Lua n'a pas d'expressions régulières au sens classique, mais offre un système de patterns (motifs) puissant et performant pour la recherche et la manipulation de chaînes.
Les fonctions de pattern matching
Les patterns Lua utilisent des séquences de caractères spéciaux pour décrire des motifs de recherche.
-- Les classes de caractères
-- %a : lettre, %d : chiffre, %w : alphanumérique
-- %s : espace blanc, %l : minuscule, %u : majuscule
local texte = "Contact : alice@example.com ou bob@site.org"
-- string.find avec pattern
local debut, fin = string.find(texte, "%a+@%a+%.%a+")
print(debut, fin) --> 11 25
print(string.sub(texte, debut, fin)) --> alice@example.com
-- Les quantificateurs
-- + : un ou plusieurs, * : zéro ou plusieurs
-- - : zéro ou plusieurs (non-gourmand), ? : zéro ou un
print(string.match("abc123def456", "%d+")) --> 123
print(string.match("abc123def456", "%d+.-")) --> 123 (non-gourmand)
Les captures (captures)
Les parenthèses dans les patterns créent des captures qui extraient des parties de la chaîne correspondante.
-- Captures simples avec parenthèses
local date = "22/07/2026"
local jour, mois, annee = string.match(date, "(%d+)/(%d+)/(%d+)")
print(jour, mois, annee) --> 22 07 2026
-- Capture avec spécificateur de classe
local tel = "Tél : 06 12 34 56 78"
local numero = string.match(tel, "(%d%d%s?%d%d%s?%d%d%s?%d%d%s?%d%d)")
print(numero) --> "06 12 34 56 78"
Patterns avancés et captures nommées
-- Les motifs spéciaux
-- ^ : début de chaîne, $ : fin de chaîne
-- [] : ensemble, [^] : complément
-- Valider une adresse email
local function validerEmail(email)
return string.match(email,
"^[%w%.%%%+%-]+@[%w%.%-]+%.[%a]+$") ~= nil
end
print(validerEmail("alice@exemple.com")) --> true
print(validerEmail("pas-un-email")) --> false
-- Captures nommées avec balises
local url = "https://www.exemple.com/page?id=42"
local scheme, host, path, params = string.match(url,
"^(%a[%w%+%-]*):/?/?([^/]+)(/?[^?]*)[?]?(.*)")
print(scheme, host, path, params)
--> https www.exemple.com /page id=42
gmatch et gsub avancés
-- string.gmatch : itérer sur toutes les occurrences
local texte = "Les chats et les chiens sont gentils"
for mot in texte:gmatch("%a+") do
print(mot)
end
-- gsub avec captures et fonctions
local texte2 = "Prix : 100 EUR, 200 EUR, 50 USD"
-- Remplacement avec fonction de transformation
local resultat = string.gsub(texte2, "(%d+)(%s?%a+)",
function(montant, devise)
return tonumber(montant) * 1.1 .. devise
end)
print(resultat)
--> "Prix : 110 EUR, 220 EUR, 55 USD"
-- gsub avec motif de remplacement
local n = select(2, string.gsub(texte, "%a+", "X"))
print(string.format("%d mots trouvés", n))
Patterns communs prêts à l'emploi
-- Extraction de nombres
local donnees = "Température: 23.5°C, Humidité: 65%"
for nb in donnees:gmatch("%d+%.?%d*") do
print(nb)
end
-- Validation de mot de passe
local function motDePasseFort(mdp)
if #mdp < 8 then return false, "Trop court" end
if not string.match(mdp, "%u") then return false, "Pas de majuscule" end
if not string.match(mdp, "%l") then return false, "Pas de minuscule" end
if not string.match(mdp, "%d") then return false, "Pas de chiffre" end
return true
end
print(motDePasseFort("abc123")) --> false "Trop court"
print(motDePasseFort("Abc12345")) --> true
Récapitulatif
- Lua utilise des patterns (pas des regex complètes) pour la recherche de texte.
string.find,string.match,string.gmatch,string.gsubsont les fonctions principales.- Les parenthèses créent des captures dans les patterns.
- Les classes de caractères :
%a,%d,%w,%s, etc. gsubaccepte des fonctions de transformation pour des remplacements complexes.
12. Projet final
Maintenant que vous maîtrisez Lua 5.5, il est temps de créer un projet complet ! Nous allons construire un pipeline de traitement de fichiers utilisant toutes les nouveautés de Lua 5.5 : <close> pour la gestion des fichiers, <const> pour la configuration, les coroutines pour le streaming, les patterns pour l'analyse, et la gestion d'erreurs robuste.
Ce que vous devez implémenter
- <const> pour les constantes de configuration et les options.
- <close> pour la fermeture automatique des fichiers et connexions.
- Coroutines pour le streaming de données et les itérateurs.
- Patterns pour l'analyse et la validation des données.
- pcall/xpcall pour la gestion d'erreurs robuste.
Le module de configuration (config.lua)
-- config.lua : Configuration avec <const>
local Config = {}
-- Constantes de configuration
local <const> CHEMIN_ENTREE = "./data/entree.csv"
local <const> CHEMIN_SORTIE = "./data/sortie.json"
local <const> TAILLE_LOT = 100
local <const> VERSION = "1.0.0"
-- Patterns de validation
local <const> PATTERNS = {
email = "^[%w%.%+%-]+@[%w%.%-]+%.[%a]+$",
telephone = "^0[1-9]%d{8}$",
date = "^%d%d%d%d%-%d%d%-%d%d$",
nombre = "^-?%d+%.?%d*$",
}
Config.CHEMIN_ENTREE = CHEMIN_ENTREE
Config.CHEMIN_SORTIE = CHEMIN_SORTIE
Config.TAILLE_LOT = TAILLE_LOT
Config.VERSION = VERSION
Config.PATTERNS = PATTERNS
return Config
Le module de traitement (processeur.lua)
-- processeur.lua : Pipeline de traitement avec coroutines
local Config = require("config")
local Processeur = {}
-- Générateur de lignes CSV avec coroutine
function Processeur.genererLignes(cheminFichier)
return coroutine.create(function()
do
local <close> f = io.open(cheminFichier, "r")
if not f then
error("Fichier non trouvé : " .. cheminFichier)
end
local enTete = nil
for ligne in f:lines() do
if enTete == nil then
enTete = ligne
else
coroutine.yield(ligne, enTete)
end
end
end
end)
end
-- Parser une ligne CSV
function Processeur.parserCSV(ligne, enTete)
local champs = {}
local i = 1
for valeur in ligne:gmatch("[^,]+") do
local nom = select(2, string.match(enTete, "([^,]+)", 1))
champs[i] = string.trim(valeur)
i = i + 1
end
return champs
end
-- Valider les données avec les patterns
function Processeur.valider(donnees)
local erreurs = {}
if donnees.email and not string.match(donnees.email, Config.PATTERNS.email) then
table.insert(erreurs, "Email invalide : " .. donnees.email)
end
if donnees.telephone and not string.match(donnees.telephone, Config.PATTERNS.telephone) then
table.insert(erreurs, "Téléphone invalide : " .. donnees.telephone)
end
if donnees.date and not string.match(donnees.date, Config.PATTERNS.date) then
table.insert(erreurs, "Date invalide : " .. donnees.date)
end
return #erreurs == 0, erreurs
end
-- Transformer les données
function Processeur.transformer(ligne)
return {
nom = string.upper(ligne.nom),
email = string.lower(ligne.email),
age = tonumber(ligne.age) or 0,
date_inscription = ligne.date,
}
end
return Processeur
Le programme principal (main.lua)
-- main.lua : Pipeline de traitement de fichiers Lua 5.5
local Config = require("config")
local Processeur = require("processeur")
print("========================================")
print(" PIPELINE DE TRAITEMENT LUA " .. Config.VERSION)
print("========================================")
-- Statistiques avec <const>
local <const> stats = {
traites = 0,
erreurs = 0,
ignores = 0,
}
-- Pipeline principal avec gestion d'erreurs
do
local ok, err = pcall(function()
-- Lancer le générateur de lignes
local gen = Processeur.genererLignes(Config.CHEMIN_ENTREE)
-- Ouvrir le fichier de sortie
local <close> sortie = io.open(Config.CHEMIN_SORTIE, "w")
if not sortie then
error("Impossible de créer le fichier de sortie")
end
sortie:write("[\n")
local premier = true
-- Parcourir le générateur
while coroutine.status(gen) ~= "dead" do
local okLigne, ligne, enTete = coroutine.resume(gen)
if okLigne and ligne then
local donnees = Processeur.parserCSV(ligne, enTete)
-- Valider
local valide, errs = Processeur.valider(donnees)
if valide then
-- Transformer
local transforms = Processeur.transformer(donnees)
stats.traites = stats.traites + 1
-- Écrire en JSON
if not premier then sortie:write(",\n") end
premier = false
sortie:write(" {")
sortie:write('"nom": "' .. transforms.nom .. '", ')
sortie:write('"email": "' .. transforms.email .. '", ')
sortie:write('"age": ' .. transforms.age)
sortie:write("}")
else
stats.erreurs = stats.erreurs + 1
print("[ERREUR] " .. table.concat(errs, "; "))
end
end
end
sortie:write("\n]\n")
print("Fichier de sortie créé : " .. Config.CHEMIN_SORTIE)
end)
if not ok then
print("[FATAL] " .. err)
end
end
-- Afficher les statistiques
print("\n--- Statistiques ---")
print("Traités : " .. stats.traites)
print("Erreurs : " .. stats.erreurs)
print("Ignorés : " .. stats.ignores)
print("Terminé !")
Les patterns pour l'analyse avancée
-- utils.lua : Fonctions utilitaires avancées avec patterns
local Utils = {}
-- Extraire toutes les URLs d'un texte
function Utils.extraireURLs(texte)
local urls = {}
for url in texte:gmatch("https?://[%w%.%-]+[%w%.%-/]*[%?%w=&%-]*") do
table.insert(urls, url)
end
return urls
end
-- Formater un nombre avec séparateurs de milliers
function Utils.formaterNombre(nb)
local texte = tostring(nb)
local resultat = texte:reverse():gsub("(%d%d%d)", "%1."):reverse()
if resultat:sub(1, 1) == "." then
resultat = resultat:sub(2)
end
return resultat
end
-- Masquer partiellement une adresse email
function Utils.maskerEmail(email)
return string.gsub(email, "(%w)([%w%.%-]*@)",
function(premier, reste)
return premier .. string.rep("*", #reste - 1) .. "@"
end)
end
return Utils
Conseils pour réussir
- Commencez par le module de configuration avec
<const>. - Implémentez le processeur de données avec les coroutines.
- Testez chaque module individuellement avec
lua -i. - Utilisez
<close>pour toutes les ressources (fichiers, connexions). - Ajoutez la gestion d'erreurs avec
pcalletxpcall. - Expérimentez : ajoutez du filtrage, du tri, des rapports.
- Utilisez les patterns pour valider et transformer les données.
- Mesurez les performances avec
os.clock().
Récapitulatif du cours
<const>: constantes et configuration sûre.<close>: nettoyage automatique des ressources.- Coroutines : générateurs et streaming de données.
- Patterns : analyse et validation de texte puissante.
pcall/xpcall: gestion d'erreurs robuste.- Vous êtes maintenant prêt à utiliser Lua 5.5 dans vos projets !