---
title: "O que são table e metatable em Lua? - API7.ai"
description: "Table é o único mecanismo de estruturação de dados em Lua. Podemos usar tables para representar arrays comuns, tabelas de símbolos, conjuntos, registros, filas e outras estruturas de dados. Saiba mais neste post."
url: "https://api7.ai/pt/learning-center/openresty/lua-table-and-metatable"
---

# O que são table e metatable em Lua?

API7.ai

October 11, 2022

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

Hoje vamos aprender sobre a única estrutura de dados no LuaJIT: a `table`.

Ao contrário de outras linguagens de script com estruturas de dados ricas, o LuaJIT possui apenas uma estrutura de dados, a `table`, que não é distinguida entre arrays, hashes, coleções, etc., mas é uma mistura de tudo. Vamos revisar um dos exemplos mencionados anteriormente.

```
local color = {first = "red", "blue", third = "green", "yellow"}
print(color["first"])                 --> output: red
print(color[1])                         --> output: blue
print(color["third"])                --> output: green
print(color[2])                         --> output: yellow
print(color[3])                         --> output: nil
```

Neste exemplo, a tabela `color` contém um array e um hash e pode ser acessada sem interferência mútua. Por exemplo, você pode usar a função `ipairs` para iterar apenas pela parte do array da tabela.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
for k, v in ipairs(color) do
      print(k)
end
'
```

As operações com `table` são tão cruciais que o LuaJIT estende a biblioteca padrão de tabelas do Lua 5.1, e o OpenResty estende ainda mais a biblioteca de tabelas do LuaJIT. Vamos dar uma olhada em cada uma dessas funções da biblioteca.

## As funções da biblioteca table

Vamos começar com as funções padrão da biblioteca table. O Lua 5.1 não tem muitas funções na biblioteca table, então podemos passar rapidamente por elas.

### `table.getn` Obter o número de elementos

Como mencionamos no capítulo [Lua Padrão e LuaJIT](https://api7.ai/learning-center/openresty/luajit-vs-lua/), obter o número correto de todos os elementos da tabela é um grande problema no LuaJIT.

Para sequências, você pode usar `table.getn` ou o operador unário `#` para retornar o número correto de elementos. O exemplo a seguir retorna o número 3, como esperado.

```
$ resty -e 'local t = { 1, 2, 3 }
  print(table.getn(t))
```

O valor correto não pode ser retornado para tabelas que não são sequenciais. No segundo exemplo, o valor retornado é 1.

```
$ resty -e 'local t = { 1, a = 2 }
  print(#t) '
```

Felizmente, tais funções difíceis de entender foram substituídas por extensões do LuaJIT, que mencionaremos mais tarde. Portanto, no contexto do OpenResty, não use a função `table.getn` e o operador unário `#`, a menos que você saiba explicitamente que está obtendo o comprimento de uma sequência.

Além disso, `table.getn` e o operador unário `#` não têm complexidade de tempo O(1), mas O(n), o que é outro motivo para evitá-los, se possível.

### `table.remove` Remove o elemento especificado

A segunda é a função `table.remove`, que remove elementos na tabela com base em subscritos, ou seja, apenas os elementos na parte do array da tabela podem ser removidos. Vamos olhar novamente o exemplo `color`.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
  table.remove(color, 1)
  for k, v in pairs(color) do
      print(v)
  end'
```

Este código removerá o `blue` com subscrito 1. Você pode perguntar: como eu removo a parte do hash da tabela? É tão simples quanto definir o valor correspondente à chave como `nil`. Assim, no exemplo `color`, o `green` correspondente a `third` é removido.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
  color.third = nil
  for k, v in pairs(color) do
      print(v)
  end'
```

### `table.concat` Função de concatenação de elementos

A terceira é a função de concatenação de elementos `table.concat`. Ela concatena os elementos da tabela de acordo com os subscritos. Como isso é novamente baseado em subscritos, ainda é para a parte do array da tabela. Novamente com o exemplo `color`.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
print(table.concat(color, ", "))'
```

Após usar a função `table.concat`, ela retorna `blue, yellow` e a parte do hash é ignorada.

Além disso, esta função também pode especificar a posição inicial do subscrito para fazer a concatenação; por exemplo, ela é escrita como a seguir:

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow", "orange"}
print(table.concat(color, ", ", 2, 3))'
```

Desta vez, a saída é `yellow, orange`, pulando `blue`.

Por favor, não subestime esta função aparentemente inútil, mas ela pode ter efeitos inesperados ao otimizar o desempenho e é uma das principais personagens em nossos capítulos posteriores de otimização de desempenho.

### `table.insert` Insere um elemento

Finalmente, vamos olhar para a função `table.insert`. Ela insere um novo elemento no subscrito especificado, o que afeta a parte do array da tabela. Para ilustrar, novamente usando o exemplo `color`.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
table.insert(color, 1,  "orange")
print(color[1])
'
```

Você pode ver que o primeiro elemento de `color` se torna `orange`, mas, claro, você pode deixar o subscrito não especificado, de modo que ele será inserido no final da fila por padrão.

Devo observar que `table.insert` é uma operação comum, mas o desempenho não é bom. Se você não estiver inserindo elementos com base no subscrito especificado, precisará chamar `lj_tab_len` do LuaJIT cada vez para obter o comprimento do array e inserir no final da fila. Como `table.getn`, a complexidade de tempo para obter o comprimento da tabela é O(n).

Portanto, para a operação `table.insert`, devemos tentar evitar usá-la em código quente. Por exemplo:

```
local t = {}
for i = 1, 10000 do
     table.insert(t, i)
end
```

## Funções de extensão da tabela do LuaJIT

A seguir, vamos olhar para as funções de extensão da tabela do LuaJIT. O LuaJIT estende o Lua padrão com duas funções úteis para criar e esvaziar uma tabela, que descreverei abaixo.

### `table.new(narray, nhash)` Cria uma nova tabela

A primeira é a função `table.new(narray, nhash)`. Em vez de crescer por si só ao inserir elementos, esta função pré-aloca o tamanho do espaço do array e do hash especificados, que é o que seus dois parâmetros `narray` e `nhash` significam. O crescimento automático é uma operação custosa que envolve alocação de espaço, `resize` e `rehash`, e deve ser evitado a todo custo.

Observe aqui que a documentação de `table.new` não está no site do LuaJIT, mas está profundamente na [documentação estendida](https://github.com/openresty/luajit2/blob/v2.1-agentzh/doc/extensions.html) do projeto no GitHub, então é difícil encontrá-la mesmo no Google, e poucos engenheiros sabem disso.

Aqui está um exemplo simples, e vou mostrar como ela funciona. Primeiro, esta função é estendida, então antes de usá-la, você precisa `require`.

```
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
   t[i] = i
end
```

Como você pode ver, este código cria uma nova tabela com 100 elementos de array e 0 elementos de hash. Claro, você pode criar uma nova tabela com 100 elementos de array e 50 elementos de hash, conforme necessário, o que é legal.

```
local t = new_tab(100, 50)
```

Alternativamente, se você ultrapassar o tamanho do espaço pré-definido, ainda poderá usá-la normalmente, mas o desempenho será degradado, e o ponto de usar `table.new` será perdido.

No exemplo a seguir, temos um tamanho pré-definido de 100, mas estamos usando 200.

```
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 200 do
   t[i] = i
end
```

Você precisa pré-definir o tamanho do espaço do array e do hash em `table.new` de acordo com o cenário real, para que possa encontrar um equilíbrio entre desempenho e uso de memória.

### `table.clear()` Limpa a tabela

A segunda é a função de limpeza `table.clear()`. Ela limpa todos os dados de uma tabela, mas não libera a memória ocupada pelas partes do array e do hash. Portanto, é útil ao reciclar tabelas Lua para evitar a sobrecarga de criar e destruir tabelas repetidamente.

```
$ resty -e 'local clear_tab =require "table.clear"
local color = {first = "red", "blue", third = "green", "yellow"}
clear_tab(color)
for k, v in pairs(color) do
     print(k)
end'
```

No entanto, há poucos cenários onde esta função pode ser usada, e na maioria dos casos, devemos deixar essa tarefa para o GC do LuaJIT.

## Funções de extensão da tabela do OpenResty

Como mencionei no início, o OpenResty mantém seu próprio branch do LuaJIT, que também estende a tabela, [com várias novas APIs](https://github.com/openresty/luajit2/#new-api): `table.isempty`, `table.isarray`, `table.nkeys` e `table.clone`.

Antes de usar essas novas APIs, verifique a versão do OpenResty, pois a maioria dessas APIs só pode ser usada em versões do OpenResty após 1.15.8.1. Isso ocorre porque o OpenResty não teve um novo lançamento por cerca de um ano antes da versão 1.15.8.1, e essas APIs foram adicionadas nesse intervalo de lançamento.

Incluí um link para o artigo, então vou usar `table.nkeys` como exemplo. As outras três APIs são fáceis de entender a partir da nomenclatura, então dê uma olhada na documentação do GitHub, e você entenderá. Devo dizer que a documentação do OpenResty é de alta qualidade, incluindo exemplos de código, se pode ser JIT, o que observar, etc. Várias ordens de magnitude melhor do que a documentação do Lua e do LuaJIT.

Ok, de volta à função `table.nkeys`. Sua nomenclatura pode confundir você, mas é uma função que obtém o comprimento da tabela e retorna o número de elementos da tabela, incluindo os elementos do array e da parte do hash. Portanto, podemos usá-la em vez de `table.getn`, por exemplo, como a seguir.

```
local nkeys = require "table.nkeys"
print(nkeys({}))  -- 0
print(nkeys({ "a", nil, "b" }))  -- 2
print(nkeys({ dog = 3, cat = 4, bird = nil }))  -- 2
print(nkeys({ "a", dog = 3, cat = 4 }))  -- 3
```

## Metatable

Depois de falar sobre a função da tabela, vamos olhar para a `metatable` derivada da `table`. A metatable é um conceito único no Lua e é amplamente usada em projetos reais. Não é exagero dizer que você pode encontrá-la em quase qualquer biblioteca `lua-resty-*`.

A `metatable` se comporta como sobrecarga de operadores; por exemplo, podemos sobrecarregar `__add` para calcular a concatenação de dois arrays Lua ou `__tostring` para definir funções que convertem para strings.

O Lua, por outro lado, fornece duas funções para lidar com metatable.

- A primeira é `setmetatable(table, metatable)`, que configura uma metatable para uma tabela.
- A segunda é `getmetatable(table)`, que obtém a metatable da tabela.

Depois de tudo isso, você pode estar mais interessado no que ela faz, então vamos ver para que a metatable é usada especificamente. Aqui está um trecho de código de um projeto real.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1,
  patch = 1
  }
version = setmetatable(version, {
    __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

Primeiro definimos uma tabela chamada `version`, e como você pode ver, o propósito deste código é imprimir o número da versão em `version`. No entanto, não podemos imprimir `version` diretamente. Você pode tentar fazer isso e verá que imprimir diretamente só retornará o endereço da tabela.

```
print(tostring(version))
```

Portanto, precisamos personalizar a função de conversão de string para esta tabela, que é `__tostring`, e é aqui que a metatable entra. Usamos `setmetatable` para redefinir o método `__tostring` da tabela `version` para imprimir o número da versão: 1.1.1.

Além de `__tostring`, frequentemente sobrecarregamos os seguintes dois metamétodos na metatable em projetos reais.

**Um deles é \_\_index**. Quando procuramos um elemento em uma tabela, primeiro o procuramos diretamente na tabela, e se não o encontrarmos, vamos para o `__index` da metatable.

No exemplo a seguir, removemos o `patch` da tabela `version`.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = function(t, key)
         if key == "patch" then
             return 2
         end
     end,
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

Neste caso, `t.patch` não obtém o valor, então ele vai para a função `__index`, que imprime 1.1.2.

`__index` pode ser não apenas uma função, mas também uma tabela, e se você tentar executar o seguinte código, verá que eles alcançam o mesmo resultado.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = {patch = 2},
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

**Outro metamétodo é \_\_call**. Ele é semelhante a um functor que permite que uma tabela seja chamada.

Vamos construir sobre o código acima que imprime o número da versão e ver como chamar uma tabela.

```
$ resty -e '
local version = {
  major = 1,
  minor = 1,
  patch = 1
  }
local function print_version(t)
     print(string.format("%d.%d.%d", t.major, t.minor, t.patch))
end
version = setmetatable(version,
     {__call = print_version})
  version()
'
```

Neste código, usamos `setmetatable` para adicionar uma metatable à tabela `version`, e o metamétodo `__call` dentro dela aponta para a função `print_version`. Portanto, se tentarmos chamar `version` como uma função, a função `print_version` será executada aqui.

E `getmetatable` é a operação emparelhada com `setmetatable` para obter a metatable que foi configurada, como no seguinte código.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = {patch = 2},
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(getmetatable(version).__index.patch)
'
```

Além desses três metamétodos que discutimos hoje, há alguns metamétodos menos usados que você pode consultar na [documentação](http://lua-users.org/wiki/MetamethodsTutorial) para aprender mais quando os encontrar.

## Orientação a Objetos

Finalmente, vamos falar sobre orientação a objetos. Como você deve saber, o Lua não é uma linguagem de Orientação a Objetos, mas podemos usar metatable para implementar OO.

Vamos ver um exemplo prático. [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql/blob/master/lib/resty/mysql.lua) é o cliente MySQL oficial do OpenResty, e ele usa metatables para **simular** classes e métodos de classe, que são usados da seguinte forma.

```
    $ resty -e 'local mysql = require "resty.mysql" -- primeiro referencie a biblioteca lua-resty
    local db, err = mysql:new() -- Cria uma nova instância da classe
    db:set_timeout(1000) -- Chamando métodos de uma classe
```

Você pode executar o código acima diretamente com o comando `resty`. Essas linhas de código são fáceis de entender; a única coisa que pode causar problemas é:

**Por que, ao chamar um método de classe, é usado dois pontos em vez de um ponto?**

Na verdade, tanto dois pontos quanto pontos são aceitáveis aqui, e `db:set_timeout(1000)` e `db.set_timeout(db, 1000)` são exatamente equivalentes. Os dois pontos são um açúcar sintático no Lua que permite omitir o primeiro argumento `self` de uma função.

Como todos sabemos, não há segredos diante do código-fonte, então vamos ver a implementação concreta correspondente às linhas de código acima para que você possa entender melhor como modelar orientação a objetos com metatables.

```
local _M = { _VERSION = '0.21' } -- Usando a tabela para simular uma classe
local mt = { __index = _M } -- mt é abreviação de metatable, __index se refere à própria classe
-- Construtor da classe
function _M.new(self)
    local sock, err = tcp()
    if not sock then
          return nil, err
    end
    return setmetatable({ sock = sock }, mt) -- exemplo de simulação de classes usando tabela e metatable
end

-- Funções membro de uma classe
function _M.set_timeout(self, timeout) -- Use o argumento self para obter uma instância da classe que deseja operar
  local sock = self.sock
  if not sock then
      return nil, "not initialized"
  end

  return sock:settimeout(timeout)
end
```

A tabela `_M` simula uma classe inicializada com uma única variável membro `_VERSION` e posteriormente define funções membro como `_M.set_timeout`. No construtor `_M.new(self)`, retornamos uma tabela cuja metatable é `mt`, e o metamétodo `__index` de `mt` aponta para `_M`, de modo que a tabela retornada simula uma instância da classe `_M`.

## Resumo

Bem, isso conclui o conteúdo principal de hoje. Tabela e metatable são amplamente usadas na biblioteca `lua-resty-*` do OpenResty e em projetos de código aberto baseados no OpenResty. Espero que esta lição facilite a leitura e compreensão do código-fonte.

Há outras funções padrão no Lua além da tabela, que aprenderemos juntos na próxima lição.

Finalmente, gostaria de deixar uma pergunta para reflexão. Por que a biblioteca `lua-resty-mysql` simula OO como uma camada de encapsulamento? Sinta-se à vontade para discutir esta questão na seção de comentários, e compartilhe este artigo com seus colegas e amigos para que possamos nos comunicar e progredir juntos.

Share article link

Share

Related Posts

[![O que torna o OpenResty tão especial](https://static.api7.ai/2022/10/14/634974e090ab4.png)](https://api7.ai/learning-center/openresty/openresty-getting-started)

## [O que torna o OpenResty tão especial](https://api7.ai/learning-center/openresty/openresty-getting-started)

[![E se houver um conflito entre as regras do Lua e a configuração do NGINX?](https://static.api7.ai/2022/10/14/634974e117ebd.png)](https://api7.ai/learning-center/openresty/conflict-between-lua-rules-and-nginx)

## [E se houver um conflito entre as regras do Lua e a configuração do NGINX?](https://api7.ai/learning-center/openresty/conflict-between-lua-rules-and-nginx)

Table of Contents

- [As funções da biblioteca table](#as-fun%C3%A7%C3%B5es-da-biblioteca-table)

  - [`table.getn` Obter o número de elementos](#tablegetn-obter-o-n%C3%BAmero-de-elementos)
  - [`table.remove` Remove o elemento especificado](#tableremove-remove-o-elemento-especificado)
  - [`table.concat` Função de concatenação de elementos](#tableconcat-fun%C3%A7%C3%A3o-de-concatena%C3%A7%C3%A3o-de-elementos)
  - [`table.insert` Insere um elemento](#tableinsert-insere-um-elemento)

- [Funções de extensão da tabela do LuaJIT](#fun%C3%A7%C3%B5es-de-extens%C3%A3o-da-tabela-do-luajit)

  - [`table.new(narray, nhash)` Cria uma nova tabela](#tablenewnarray-nhash-cria-uma-nova-tabela)
  - [`table.clear()` Limpa a tabela](#tableclear-limpa-a-tabela)

- [Funções de extensão da tabela do OpenResty](#fun%C3%A7%C3%B5es-de-extens%C3%A3o-da-tabela-do-openresty)

- [Metatable](#metatable)

- [Orientação a Objetos](#orienta%C3%A7%C3%A3o-a-objetos)

- [Resumo](#resumo)
