---
title: "Conseils pratiques : Identifier les concepts uniques et les pièges en Lua - API7.ai"
description: "Voici quelques concepts uniques en Lua, par exemple, Weak Table, Closure, et upvalue."
url: "https://api7.ai/fr/learning-center/openresty/lua-basic-concepts-and-pitfalls"
---

# Conseils pratiques : Identifier les concepts uniques et les pièges en Lua

API7.ai

October 12, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

Dans l'article précédent, nous avons appris les fonctions de bibliothèque liées aux tables dans LuaJIT. En plus de ces fonctions courantes, aujourd'hui, je vais vous présenter quelques concepts uniques ou moins courants de Lua ainsi que les pièges courants de Lua dans OpenResty.

## Table Faible

Tout d'abord, il y a la *table faible*, un concept unique en Lua, qui est lié au ramasse-miettes. Comme d'autres langages de haut niveau, Lua dispose d'un ramasse-miettes automatique, vous n'avez pas à vous soucier de son implémentation, et vous n'avez pas à effectuer un GC explicitement. Le ramasse-miettes collectera automatiquement l'espace qui n'est pas référencé.

Mais le simple comptage de références ne suffit pas toujours, et parfois nous avons besoin d'un mécanisme plus flexible. Par exemple, si nous insérons un objet Lua `Foo` (*table* ou fonction) dans la table `tb`, cela crée une référence à cet objet `Foo`. Même s'il n'y a pas d'autre référence à `Foo`, la référence dans `tb` existera toujours, donc il n'y a aucun moyen pour le GC de récupérer la mémoire occupée par `Foo`. À ce stade, nous n'avons que deux options.

- L'une est de libérer `Foo` manuellement.
- La seconde est de le laisser résider en mémoire.

Par exemple, le code suivant.

```
$ resty -e 'local tb = {}
tb[1] = {red}
tb[2] = function() print("func") end
print(#tb) -- 2

collectgarbage()
print(#tb) -- 2

table.remove(tb, 1)
print(#tb) -- 1
```

Cependant, je pense que vous ne voulez pas garder la mémoire occupée par des objets que vous n'utilisez pas, surtout que LuaJIT a une limite de mémoire de 2G. Le moment de la libération manuelle n'est pas facile et ajoute de la complexité à votre code.

C'est alors que la *table faible* entre en jeu. Regardez son nom, *table faible*. D'abord, c'est une table, et ensuite tous les éléments de cette table sont des références faibles. Le concept est toujours abstrait, alors commençons par regarder un morceau de code légèrement modifié.

```
$ resty -e 'local tb = {}
tb[1] = {red}
tb[2] = function() print("func") end
setmetatable(tb, {__mode = "v"})
print(#tb)  -- 2

collectgarbage()
print(#tb) -- 0
'
```

Comme vous pouvez le voir, les objets qui ne sont pas utilisés sont libérés. Le plus important ici est la ligne de code suivante.

```
setmetatable(tb, {__mode = "v"})
```

Cela vous rappelle quelque chose ? N'est-ce pas l'opération d'une métatable ? Oui, une table est une `table faible` lorsqu'elle a un champ `__mode` dans sa métatable.

- Si la valeur de `__mode` est `k`, la clé de la table est une référence faible.
- Si la valeur de `__mode` est `v`, alors la valeur de la table est une référence faible.
- Bien sûr, vous pouvez également la définir à `kv`, indiquant que les clés et les valeurs de cette table sont des références faibles.

L'une de ces trois *tables faibles* verra son objet clé-valeur entier récupéré une fois que sa clé ou sa valeur sera récupérée.

Dans l'exemple de code ci-dessus, la valeur de `__mode` est `v`, `tb` est un tableau, et la valeur du tableau est la table et l'objet fonction afin qu'ils puissent être recyclés automatiquement. Cependant, si vous changez la valeur de `__mode` en `k`, elle ne sera pas libérée, par exemple, si vous regardez le code suivant.

```
$ resty -e 'local tb = {}
tb[1] = {red}
tb[2] = function() print("func") end
setmetatable(tb, {__mode = "k"})
print(#tb)  -- 2

collectgarbage()
print(#tb) -- 2
'
```

Nous ne démontrons que les *tables faibles* où la valeur est une référence faible, c'est-à-dire les *tables faibles* de type tableau. Naturellement, vous pouvez également construire une *table faible* de type table de hachage en utilisant un objet comme clé, par exemple, comme suit.

```
$ resty -e 'local tb = {}
tb[{color = red}] = "red"
local fc = function() print("func") end
tb[fc] = "func"
fc = nil

setmetatable(tb, {__mode = "k"})
for k,v in pairs(tb) do
     print(v)
end

collectgarbage()
print("----------")
for k,v in pairs(tb) do
     print(v)
end
'
```

Après avoir appelé manuellement `collectgarbage()` pour forcer le GC, tous les éléments de la table `tb` auront été libérés. Bien sûr, dans le code réel, nous n'avons pas besoin d'appeler `collectgarbage()` manuellement, il s'exécutera automatiquement en arrière-plan, et nous n'avons pas à nous en soucier.

Cependant, puisque nous avons mentionné la fonction `collectgarbage()`, je vais en dire quelques mots de plus. Cette fonction peut recevoir plusieurs options différentes et par défaut, elle utilise `collect`, qui est un GC complet. Une autre option utile est `count`, qui retourne la quantité d'espace mémoire occupé par Lua. Cette statistique est utile pour voir s'il y a une fuite de mémoire et nous rappelle de ne pas approcher la limite supérieure de 2G.

Le code lié aux tables faibles est plus compliqué à écrire en pratique, moins facile à comprendre, et par conséquent, contient plus de bugs cachés. Pas besoin de se précipiter. Plus tard, je présenterai un projet open source, utilisant les tables faibles pour résoudre un problème de fuite de mémoire.

## Fermeture et upvalue

Passons aux fermetures et aux upvalues, comme je l'ai souligné précédemment, toutes les valeurs sont des citoyens de première classe en Lua, y compris les fonctions. Cela signifie que les fonctions peuvent être stockées dans des variables, passées comme arguments et retournées comme valeurs d'une autre fonction. Par exemple, cet exemple de code apparaît dans la table faible ci-dessus.

```
tb[2] = function() print("func") end
```

C'est une fonction anonyme qui est stockée comme valeur d'une table.

En Lua, la définition des deux fonctions dans le code suivant est équivalente. Cependant, notez que la dernière assigne une fonction à une variable, une méthode que nous utilisons souvent.

```
local function foo() print("foo") end
local foo = fuction() print("foo") end
```

De plus, Lua supporte l'écriture d'une fonction à l'intérieur d'une autre fonction, c'est-à-dire des fonctions imbriquées, comme dans l'exemple de code suivant.

```
$ resty -e '
local function foo()
     local i = 1
     local function bar()
         i = i + 1
         print(i)
     end
     return bar
end

local fn = foo()
print(fn()) -- 2
'
```

Vous pouvez voir que la fonction `bar` peut lire la variable locale `i` à l'intérieur de la fonction `foo` et modifier sa valeur, même si la variable n'est pas définie à l'intérieur de `bar`. Cette fonctionnalité est appelée portée lexicale.

Ces fonctionnalités de Lua sont la base des fermetures. Une fermeture est simplement une fonction qui accède à une variable dans la portée lexicale d'une autre fonction.

Par définition, toutes les fonctions en Lua sont en fait des fermetures, même si vous ne les imbriquez pas. C'est parce que le compilateur Lua prend en dehors du script Lua et l'enveloppe avec une autre couche de la fonction principale. Par exemple, les lignes de code simples suivantes.

```
local foo, bar
local function fn()
     foo = 1
     bar = 2
end
```

Après compilation, cela ressemblera à ceci.

```
function main(...)
     local foo, bar
     local function fn()
         foo = 1
         bar = 2
     end
end
```

Et la fonction `fn` capture deux variables locales de la fonction principale, donc c'est aussi une fermeture.

Bien sûr, nous savons que le concept de fermeture existe dans de nombreux langages, et il n'est pas unique à Lua, donc vous pouvez comparer et contraster pour mieux comprendre. Ce n'est qu'en comprenant les fermetures que vous pouvez comprendre ce que nous allons dire sur `upvalue`.

`upvalue` est un concept unique à Lua, qui est la variable en dehors de la portée lexicale capturée dans la fermeture. Continuons avec le code ci-dessus.

```
local foo, bar
local function fn()
     foo = 1
     bar = 2
end
```

Vous pouvez voir que la fonction `fn` capture deux variables locales, `foo` et `bar`, qui ne sont pas dans leur propre portée lexicale et que ces deux variables sont, en fait, les `upvalue` de la fonction `fn`.

## Pièges Courants

Après avoir introduit quelques concepts en Lua, je vais parler des pièges liés à Lua que j'ai rencontrés dans le développement OpenResty.

Dans la section précédente, nous avons mentionné certaines des différences entre Lua et d'autres langages de développement, comme l'indexation commençant à `1`, les variables globales par défaut, etc. Dans le développement de code réel d'OpenResty, nous rencontrerons plus de problèmes liés à Lua et LuaJIT, et je vais en parler quelques-uns des plus courants ci-dessous.

Voici un rappel que même si vous connaissez tous les pièges, vous devrez inévitablement les traverser vous-même pour être impressionné. La différence, bien sûr, est que vous serez capable de sortir du trou et de trouver le nœud du problème de manière beaucoup plus efficace.

### L'index commence-t-il à 0 ou 1 ?

Le premier piège est que l'indexation de Lua commence à `1`, comme nous l'avons mentionné à plusieurs reprises auparavant.

Mais je dois dire que ce n'est pas toute la vérité. Parce que dans LuaJIT, les tableaux créés avec `ffi.new` sont indexés à partir de `0` à nouveau :

```
local buf = ffi_new("char[?]", 128)
```

Donc, si vous voulez accéder au `buf` `cdata` dans le code ci-dessus, rappelez-vous que l'index commence à `0`, pas `1`. Soyez particulièrement attentif à cet endroit lorsque vous utilisez FFI pour interagir avec C.

### Correspondance de Modèle Régulier

Le deuxième piège est le problème de correspondance de modèle régulier, et il y a deux ensembles de méthodes de correspondance de chaînes en parallèle dans OpenResty : la bibliothèque string de Lua et l'API `ngx.re.*` d'OpenResty.

La correspondance de modèle régulier de Lua est son format unique et est écrite différemment de PCRE. Voici un exemple simple.

```
resty -e 'print(string.match("foo 123 bar", "%d%d%d"))'  — 123
```

Ce code extrait la partie numérique de la chaîne, et vous remarquerez qu'elle est complètement différente de nos expressions régulières familières. La bibliothèque de correspondance régulière de Lua est coûteuse à maintenir et peu performante - JIT ne peut pas l'optimiser, et les modèles qui ont été compilés une fois ne sont pas mis en cache.

Donc, lorsque vous utilisez la bibliothèque string intégrée de Lua pour `find`, `match`, etc., n'hésitez pas à utiliser `ngx.re` d'OpenResty à la place si vous avez besoin de quelque chose comme une expression régulière. Lorsque vous recherchez une chaîne fixe, nous envisageons d'utiliser le mode simple pour appeler la bibliothèque string.

Voici une suggestion : Dans OpenResty, nous privilégions toujours l'API d'OpenResty, puis l'API de LuaJIT, et utilisons les bibliothèques Lua avec prudence.

### L'encodage JSON ne distingue pas entre tableau et dictionnaire

Le troisième piège est que l'encodage JSON ne distingue pas entre tableau et dictionnaire ; puisque Lua n'a qu'une seule structure de données, *table*, lorsque JSON encode une table vide, il n'y a aucun moyen de déterminer si c'est un tableau ou un dictionnaire.

```
resty -e 'local cjson = require "cjson"
local t = {}
print(cjson.encode(t))
'
```

Par exemple, le code ci-dessus affiche `{}`, ce qui montre que la bibliothèque `cjson` d'OpenResty encode une table vide comme un dictionnaire par défaut. Bien sûr, nous pouvons changer ce comportement global par défaut en utilisant la fonction `encode_empty_table_as_object`.

```
resty -e 'local cjson = require "cjson"
cjson.encode_empty_table_as_object(false)
local t = {}
print(cjson.encode(t))
'
```

Cette fois, la table vide est encodée comme un tableau `[]`.

Cependant, ce réglage global a un impact significatif, alors pouvons-nous spécifier les règles d'encodage pour une table particulière ? La réponse est naturellement oui, et il y a deux façons de le faire.

La première façon est d'assigner le `userdata` `cjson.empty_array` à la table spécifiée afin qu'elle soit traitée comme un tableau vide lors de l'encodage JSON.

```
$ resty -e 'local cjson = require "cjson"
local t = cjson.empty_array
print(cjson.encode(t))
'
```

Cependant, parfois nous ne sommes pas sûrs si la table spécifiée est toujours vide. Nous voulons l'encoder comme un tableau lorsqu'elle est vide, donc nous utilisons la fonction `cjson.empty_array_mt`, qui est notre deuxième méthode.

Elle marquera la table spécifiée et l'encodera comme un tableau lorsque la table est vide. Comme vous pouvez le voir par le nom `cjson.empty_array_mt`, elle est définie en utilisant une `metatable`, comme dans l'opération de code suivante.

```
$ resty -e 'local cjson = require "cjson"
local t = {}
setmetatable(t, cjson.empty_array_mt)
print(cjson.encode(t))
t = {123}
print(cjson.encode(t))
'
```

### Limite sur le nombre de variables

Regardons le quatrième piège, la limite sur le nombre de variables. Lua a une limite supérieure sur le nombre de variables locales et le nombre d'`upvalue` dans une fonction, comme vous pouvez le voir dans le code source de Lua.

```
/*
@@ LUAI_MAXVARS est le nombre maximum de variables locales par fonction
@* (doit être inférieur à 250).
*/
#define LUAI_MAXVARS            200

/*
@@ LUAI_MAXUPVALUES est le nombre maximum d'upvalues par fonction
@* (doit être inférieur à 250).
*/
#define LUAI_MAXUPVALUES        60
```

Ces deux seuils sont codés en dur à `200` et `60`, respectivement, et bien que vous puissiez modifier manuellement le code source pour ajuster ces deux valeurs, elles ne peuvent être définies qu'à un maximum de `250`.

Généralement, nous ne dépassons pas ce seuil. Cependant, lorsque vous écrivez du code OpenResty, vous devez faire attention à ne pas abuser des variables locales et des `upvalue`, mais à utiliser `do ... end` autant que possible pour réduire le nombre de variables locales et d'`upvalue`.

Par exemple, regardons le pseudo-code suivant.

```
local re_find = ngx.re.find
function foo() ... end
function bar() ... end
function fn() ... end
```

Si seule la fonction `foo` utilise `re_find`, alors nous pouvons la modifier comme suit :

```
do
    local re_find = ngx.re.find
    function foo() ... end
end
function bar() ... end
function fn() ... end
```

## Résumé

Du point de vue de "poser plus de questions", d'où vient le seuil de `250` dans Lua ? C'est notre question de réflexion pour aujourd'hui. Vous êtes invités à laisser vos commentaires et à partager cet article avec vos collègues et amis. Nous communiquerons et améliorerons ensemble.

Share article link

Share

Related Posts

[![Qu'est-ce qui rend OpenResty si spécial ?](https://static.api7.ai/2022/10/14/634974e090ab4.png)](https://api7.ai/learning-center/openresty/openresty-getting-started)

## [Qu'est-ce qui rend OpenResty si spécial ?](https://api7.ai/learning-center/openresty/openresty-getting-started)

[![Que se passe-t-il en cas de conflit entre les règles Lua et la configuration NGINX ?](https://static.api7.ai/2022/10/14/634974e117ebd.png)](https://api7.ai/learning-center/openresty/conflict-between-lua-rules-and-nginx)

## [Que se passe-t-il en cas de conflit entre les règles Lua et la configuration NGINX ?](https://api7.ai/learning-center/openresty/conflict-between-lua-rules-and-nginx)

Table of Contents

- [Table Faible](#table-faible)

- [Fermeture et upvalue](#fermeture-et-upvalue)

- [Pièges Courants](#pi%C3%A8ges-courants)

  - [L'index commence-t-il à 0 ou 1 ?](#lindex-commence-t-il-%C3%A0-0-ou-1-)
  - [Correspondance de Modèle Régulier](#correspondance-de-mod%C3%A8le-r%C3%A9gulier)
  - [L'encodage JSON ne distingue pas entre tableau et dictionnaire](#lencodage-json-ne-distingue-pas-entre-tableau-et-dictionnaire)
  - [Limite sur le nombre de variables](#limite-sur-le-nombre-de-variables)

- [Résumé](#r%C3%A9sum%C3%A9)
