Lu-a-Bá - O bê-a-bá de Lua para a comunidade lusófona

Lua e C

C é a base do ambiente Lua, já que o interpretador Lua é implementado em C, e a sintaxe da linguagem Lua possui algumas semelhanças com a sintaxe de C. Além disso, a biblioteca Lua é integrada a outros ambientes de programação por uma API C.

Chamo atenção para o fato de que saber programar em C não é um pré-requisito para programar em Lua (embora possa ajudar a ter uma percepção mais abrangente da linguagem). No entanto, a depender do objetivo do programador, integrar código C com código Lua poderá ser necessário, de modo que as duas linguagens sejam usadas no mesmo programa.

Diferenças e semelhanças

Lua e C são linguagens complementares, podemos dizer até que são simbióticas. Não é incomum que programadores Lua sejam também programadores C. Por um lado, Lua oferece uma maior ergonomia e praticidade, ao passo que C privilegia um maior controle e desempenho. Usando as duas em conjunto, é possível obter o melhor de cada ambiente.

Ainda assim, há que se destacar, as linguagens possuem diferenças significativas, até mesmo para que possam refletir com eficácia os seus objetivos. Os valores fundacionais dessas duas linguagens possuem algumas diferenças, e esses valores se refletem nas escolhas feitas em suas arquiteturas. Escolhas essas que serão destacadas nos tópicos adiante.

Tipos

Uma diferença muito importante a ser notada entre as duas linguagens é que C trabalha com tipos fixos, e Lua não. O que isso significa na prática?

Basicamente, isso significa que em C os tipos de dados estão associados a variáveis, enquanto em Lua, os tipos estão associados aos valores contidos nas variáveis. Em C, uma variável terá sempre o mesmo tipo, em Lua, uma variável poderá mudar de tipo quando mudar de valor.

Essa diferença é um dos reflexos das escolhas arquiteturais. C privilegia o controle, ao passo que Lua privilegia a flexibilidade. Como consequência, C requer um planejamento prévio dos tipos das variáveis e constrangerá o programador a atribuir apenas valores compatíveis com essas variáveis, ao longo do programa, podendo provocar erros de compilação quando não o fizer.

Compiladores não toleram erros?

O código a seguir, por exemplo, falhará na compilação:

char texto[] = "Mais um ano se passou";
texto = 2;

Note que neste caso, a variável foi declarada inicialmente como um vetor de caracteres, e posteriormente o programa tenta atribuir um número inteiro a esta variável. Sendo um valor incompatível com o tipo declarado, um erro será emitido.

Um programa equivalente em Lua seria:

texto = "Mais um ano se passou"
texto = 2

Este programa seria executado sem erros pelo interpretador Lua. Note que não há declaração de tipos neste caso, pois uma variável em Lua pode guardar qualquer tipo de valor, (lembrando, o tipo é do valor, não da variável). Este trecho num primeiro momento armazena um texto na variável, mas depois usa a mesma variável para guardar um número inteiro.

Essa diferença pode parecer um defeito em C e uma vantagem em Lua, mas não se engane: raramente uma escolha arquitetural terá apenas benefícios. Tipos fixos podem parecer engessados demais, mas isto garante que a variável terá o mesmo tipo ao longo do programa, permitindo que você possa prever com mais acurácia como o programa vai se comportar.

Em Lua, não é preciso ter essa preocupação inicial com o tipo da variável, porém dependendo da operação a ser feita com ela, será necessário implementar algumas verificações de tipo, a fim de evitar que se trabalhe com um tipo inadequado para a operação em questão.

Neste sentido, deve-se ter o cuidado de checar se o tipo de texto efetivamente é textual caso essa variável seja posteriormente (como leva a crer o seu nome) usada em operações que envolvam texto, o que pode ser feito com a função type. Por exemplo:

if (type(texto) == "string") then
    -- operação com a variável texto
else
    print("Esta variável não é textual!")
end

Números

C oferece diferentes tipos de representação numérica, e utiliza operações diferentes para algumas dessas representações. Por exemplo, há uma divisão entre tipos inteiros e tipos de ponto flutuante. Os tipos de ponto flutuante são capazes de representar valores fracionários, enquanto os tipos inteiros, como o próprio nome leva a crer, não podem.

As operações em nível de processador variam significativamente quando há números de ponto flutuante envolvidos e quando não há. Outra diferença importante entre esses tipos é a capacidade dos tipos de ponto flutuante de representar números muito grandes, bem como frações muito pequenas.

A essa altura, você talvez pense que os tipos de ponto flutuante só oferecem vantagens. Mas não é bem assim. Eles tem uma característica negativa que é a não garantia de precisão. Alguns valores fracionários não podem ser representados devido a limites no modo como essa representação é feita, e nestes casos é feito um arredondamento.

Para muitos casos, isto não é um grande problema, mas quando é preciso uma garantia de precisão numérica (como por exemplo em finanças), este tipo de representação é inadequado.

Outro aspecto negativo desta representação é que ela necessita um maior número de bits para representar os mesmos valores que seus equivalentes inteiros. Isto é um detalhe que diz respeito mais à implementação do que à linguagem em si, mas é um aspecto que não deve ser ignorado.

Tanto os tipos inteiros quanto os de ponto flutuante podem ter diferentes tamanhos, de modo que sejam capazes de representar uma maior ou menor quantidade de números. No caso dos números de ponto flutuante, isso também afeta a precisão dos números.

Ok, até aqui só falamos em C. E em Lua?

Por muito tempo, todos os números em Lua usaram internamente uma única representação (ponto flutuante, por padrão, porém podendo ser configurado durante a compilação para usar outro tipo). A partir da versão 5.3 isso mudou um pouco. Desde então, por padrão, números são inteiros, exceto quando possuem uma parte fracionária.

Portanto, no exemplo a seguir, temos uma variável guardando um número inteiro e outra guardando um número fracionário (e portanto de ponto flutuante), em Lua:

inteiro = 23
flutuante = 12.5

Relembrando, em Lua, os tipos são vinculados ao valor, não à variável. Portanto, ao atribuir um número inteiro a uma variável, ela será do tipo inteiro. Ao atribuir um número fracionário, ela será do tipo ponto flutuante. Tal como ocorre em C, a aritmética de ponto flutuante será aplicada quando houver números de ponto flutuante envolvidos.

Texto

A informação textual, na maior parte das linguagens de programação, é representada por uma cadeia (fila) de caracteres, o que não é muito diferente de uma representação analógica.

É comum o uso do termo string (do inglês, corda), em referência a uma corda que une os caracteres em um único texto, de modo enfileirado. Ainda assim, existem diferenças sutis nesse tipo de representação entre diferentes linguagens, diferenças essas que existem também entre C e Lua.

Cadeias de caracteres (portanto, strings) em C são vetores de caracteres, com tamanho fixo, e sempre terminados com zero binário, isto é, nulo (vide o equivalente a zero na tabela ASCII). A representação literal de texto em C é sempre colocada entre aspas duplas.

Exemplo C:

/*
    Variável texto é um vetor de 6 caracteres
    (incluindo o zero binário no final)
*/
char texto[] = "Texto";

/*
    Também é possível representar explicitamente
    como uma lista de caracteres (note que neste
    caso é necessário ser explícito também na
    inclusão do zero binário)
*/
char outrotexto[] = { 'T', 'e', 'x', 't', 'o', '\0' };
/*
    E ainda pode representar também assim:
    (porém este último texto é imutável)
*/
char *aindaoutro = "Texto";

Neste exemplo, as variáveis texto, outrotexto e aindaoutro possuem exatamente o mesmo valor, ao final, muda apenas o modo de expressar. Entretanto, aindaoutro é um texto imutável.

Não detalharemos este aspecto, já que o nosso foco aqui é Lua, porém vale dizer que para as duas primeiras representações, é possível alterar caracteres individuais dentro do texto, de modo que o exemplo a seguir funciona.

char texto[] = "Texto";
texto[2] = 's';
texto[4] = 'e';

Ao fim, o valor de texto será “Teste”.

Em Lua, cadeias de caracteres são tratadas como um tipo básico, isto é, não é preciso denotar uma composição de outros tipos (caracteres), até porque em Lua não há a noção de caracteres como um tipo individual. Essas cadeias podem ser representadas literalmente tanto por texto entre aspas duplas como entre aspas simples. No entanto, é considerado boa prática manter um padrão, e usar sempre um formato ou outro.

Exemplo Lua:

texto = "Texto"

Neste caso, texto armazena uma variável do tipo string, e como isto é representado internamente pelo interpretador não diz respeito à linguagem propriamente (e sim à especificação do interpretador lua), portanto tipicamente não será uma preocupação.

Cadeias de caracteres em Lua são sempre imutáveis, isto é, não é possível alterar caracteres individuais dentro de um texto. Em vez disso, o que se pode fazer é gerar um novo texto, com base no anterior, mas fazendo as substituições devidas. A biblioteca padrão de Lua oferece funções para esse tipo de manipulação.

Nulos

Muitas linguagens possuem um valor/tipo específico para denotar a ausência de valor.

A linguagem C possui uma macro chamada NULL. Porém ela não é usada para qualquer situação, apenas para representar ponteiros que não apontam para lugar nenhum (ponteiros nulos). Todas as demais variáveis sempre terão algum valor, (mesmo que esse valor seja indeterminado).

int a;          // valor inteiro indeterminado
int *b = NULL;  // ponteiro nulo

Lua, em contrapartida tem um tipo específico para denotar a ausência de valores, o tipo nil. Existe apenas um único valor em Lua que corresponde a esse tipo, o valor nil. Qualquer variável em Lua pode assumir esse valor, e toda variável ainda não declarada/inicializada possui esse valor.

a = nil
-- b não foi declarado
if a == b then
    print("Este comando será executado.")
end
</code></pre>

O valor nil tem como característica ser diferente de todos os demais tipos, logo qualquer comparação com nil resultará falsa, salvo se o elemento comparado também for nil. Outra característica dele é que, como expressão, é um valor falso.

if (nil) then
    print("Isto nunca será executado")
end

Como se pode imaginar, valores nulos podem ser motivo de problemas, quando usados em situações que esperam um valor não nulo. Em Lua, é prudente checar pela presença de um valor não nulo, antes de utilizar uma variável nesse tipo de situação, ou utilizar um valor padrão, quando isto fizer sentido, como no exemplo a seguir.

mensagem = mensagem or "Olá"

Testes lógicos

Testes lógicos, que resultam verdadeiros ou falsos estão presentes em quase todas as linguagens de programação, dado que são uma operação bastante básica da computação, operada pelos microprocessadores. Porém, o modo como esses resultados são representados varia consideravelmente, especialmente quando se trata de quais valores são considerados verdadeiros e quais são considerados falsos.

Dito isso, é bastante comum que o número zero (0) seja tratado como um valor falso, ao passo que outros números sejam tratados como verdadeiros, e essa é a realidade em C. A linguagem C, inclusive, não oferece, em sua especificação, um tipo booleano, e em vez disso, é comum o uso de 0 e 1 para denotar os valores falso e verdadeiro, respectivamente.

Em Lua, isso muda um pouco. A começar pela existência do tipo boolean, que pode assumir os valores true (verdadeiro) e false (falso). Para além disso, outros valores também podem denotar um resultado verdadeiro ou falso. E aqui um ponto muito importante precisa ser destacado: o número zero, em Lua, é considerado uma expressão verdadeira, diferentemente do que representaria em C.

Os únicos valores que representam um resultado falso em Lua são false (do tipo boolean), e nil (do tipo nil).

Tipos compostos

Em C, valoes compostos geralmente são de um mesmo tipo (vetores e matrizes) ou estruturas.

Em Lua, o único tipo complexo que existe são tabelas. Elas podem ser usadas tanto para criar vetores como para criar estruturas ou mesmo uma combinação de ambos. Trata-se de um tipo altamente versátil, e que pode ser usado para construir várias outras estruturas derivadas. Podemos dizer até mesmo que seja um metatipo.

Pode-se argumentar que Lua também conta com o tipo userdata, que pode ser tratado como um tipo complexo. E esta pode ser uma maneira de enxergar a questão. No entanto, do ponto de vista de linguagem, o tipo userdata é definido em C, não em Lua.

Índices

Um ponto que causa bastante confusão é o fato de que na maioria das linguagens de programação, vetores iniciam a contagem dos índices a partir de zero (0). Em Lua, é diferente: a contagem sempre inicia por um (1), quando não explicitamente determinado de outra forma.

Vale lembrar, vetores em Lua são tabelas cujos índices são números inteiros. Nada impede que se crie itens indexados pelo número zero dentro de uma tabela. No entanto, a maior parte das funções em Lua que manipulam tabelas convencionam que se inicie por um.

Outro aspecto importante a ser mencionado sobre índices de tabelas em Lua é o de que ao criar um índice numérico de valor elevado (digamos 999999999999), isto não alocará um bloco de memória com essa quantidade de posições, apenas um item específico será alocado em memória, o que significa que tabelas Lua são estruturas leves e adequadas para criar matrizes esparsas.

Exemplo:

local t = {}
t[999999999999] = 1 -- apenas um valor guardado em memória

Em diversos outros ambientes, operações desse tipo fatalmente alocariam (ou tentariam alocar) uma estrutura capaz de abarcar essa quantidade de itens primeiro, para então guardar o valor em questão, incorrendo em um alto consumo de memória. Em C, especificamente, seria necessário alocar um vetor com este tamanho previamente.

Ponteiros

Calma! Lua não possui ponteiros (ao menos não de um modo que o programador precise se preocupar com eles). Em Lua, não há alocação (nem liberação) manual de memória, e endereços de memória não são manipulados diretamente.

Ainda assim, compreender o conceito pode ser útil ao programar em Lua, pois em Lua também existe a dualidade passagem por valor e passagem por referência. O que muda é que em Lua alguns tipos de valor são sempre passados como uma cópia (isto é, por valor) e outros sempre por referência. Como regra geral, os valores imutáveis são sempre passados por valor, enquanto os valores mutáveis são passados por referência.

Operadores

Lua e C tem alguns operadores em comum, mas como esperado, também possuem algumas diferenças nesse quesito:

Operadores ternários

Em C, existe o conceito de operador ternário, que avalia uma expressão, e retorna um valor se verdadeira, e outro, se falsa.

int x = 1;
char *y = x > 0 ? "maior" : "menor";

Neste exemplo, o valor de y será “maior”, visto que o valor de x é maior do que 0. Caso o valor de x fosse 0, por exemplo, então o valor de y seria “menor”.

Lua não possui esse tipo de operador, entretanto é possível obter um efeito similar com o uso dos operadores lógicos. Um exemplo similar ao trecho em C apresentado anteriormente seria:

x = 1
y = x > 0 and "maior" or "menor"

A única diferença desse tipo de construção para o operador ternário é que caso o primeiro valor após a expressão avaliada seja falso, a segunda expressão é que seria retornada. Por exemplo:

y = x > 0 and nil or 2

Neste caso, o valor retornado seria sempre 2, pois nil é avaliado como um valor falso. No operador ternário de C, o primeiro valor sempre será retornado quando a expressão for verdadeira.

Funções

Tanto em C como em Lua é possível definir funções, de modo a abstrair e reutilizar partes do programa cuja execução é passível de repetição, e ainda com a possibilidade de parametrizar essa execução. No entanto, existem algumas diferenças fundamentais no modo como essas duas linguagens tratam funções.

Assinatura

Em primeiro lugar, as funções em C possuem uma assinatura bem definida, isto é, uma função C possui um retorno específico e parâmetros específicos, de modo que é possível ter maior previsibilidade quanto a suas entradas e saídas. Por exemplo:

int soma(int a, int b)
{
    return a + b;
}

A função soma(), neste caso possui um retorno específico, do tipo inteiro, e possui dois dois parâmetros do tipo inteiro, cada um. Não é possível chamar a função com um número menor ou maior de parâmetros. Isto resultaria em erros de compilação, visto que a chamada da função deve ser compatível com a sua assinatura.

Além disso, os argumentos recebidos pela função (entenda-se, valores passados na chamada da função) devem ser compatíveis com os parâmetros esperados, de modo que não é possível, por exemplo, passar uma cadeia de caracteres como argumento para a função soma():

soma("a", 4); /* ERRO! */

Um texto, vale lembrar, em C é representado por um ponteiro para um bloco de memória dividido em caracteres (portanto é do tipo char *). Caracteres, por outro lado, em C são internamente representados por números e podem ser convertidos implicitamente para números, de modo que uma diferença sutil na chamada acima não produziria erros:

soma('a', 4);

Observe que as aspas duplas foram trocadas por aspas simples, denotando um caractere individual e não uma cadeia. Neste caso, uma conversão implícita é possível, e portanto C tolera esse tipo de chamada sem problema nenhum. O mesmo ocorreria, por exemplo, se um valor de ponto flutuante fosse passado, porém neste caso a parte fracionária seria perdida.

Ainda outro aspecto importante de se observar é que o retorno da função soma() será sempre inteiro, seja qual for a chamada realizada. Isto é, os argumentos passados, sejam eles quais forem, não afetam o tipo de retorno da função, que será sempre um número inteiro. Por este motivo dizemos, neste caso, que a função soma() é do tipo inteiro, pois inteiro é o tipo do seu retorno.

Certamente, esses conceitos são bastante conhecidos por um programador C, porém é conveniente recapitular esta questão, para que possamos observar o que muda em Lua. Em Lua, não há uma assinatura fixa, de modo que não necessariamente é possível prever o tipo de retorno de uma chamada de função, pois isto é um pouco mais dinâmico. Por exemplo:

function teste(num)
    if (num == 1) then
        return "OK"
    else
        return 0
    end
end

Observe que neste exemplo, a função teste() pode retornar um texto (“OK”) ou um número (0). Isto dependerá do que for passado como argumento. Deste modo, só é possível saber o que uma função retornará se antes observarmos a chamada realizada.

Além disso, é possível informar mais ou menos do que um argumento. Se mais, os argumentos adicionais são simplesmente descartados pela função. Se menos, os parâmetros esperados receberão o valor nil.

Vejamos alguns exemplos de chamadas à função teste():

teste(1)        --> "OK"
teste(0)        --> 0
teste("zero")   --> 0
teste(1, 2, 3)  --> "OK" (2 e 3 descartados)
teste()         --> 0 (parâmetro num é nil)

Neste caso, observamos que tanto a entrada quanto a saída das funções em Lua pode variar. Isto, por um lado dá ao programador Lua muito mais flexibilidade. Por outro lado, aumenta a imprevisibilidade da execução do programa. Como já foi falado em outras partes deste documento, tudo depende do objetivo. Cada escolha é uma renúncia.

Mas também friso novamente que podemos implementar alguns controles para conter essa imprevisibilidade, ou ao menos os danos que ela pode causar. Lembra-se da função type() que mencionei antes? Pois bem, ela também pode ser usada para garantir que os argumentos de uma função sejam do tipo esperado.

Por exemplo, vamos reimplementar a função soma() em Lua:

function soma(a, b)
    assert(type(a) == "number")
    assert(type(b) == "number")
    return a + b
end

Note que agora temos algumas chamadas à função assert() (que faz um teste lógico e interrompe a execução, caso resulte falso), que por sua vez utiliza a função type() para garantir que os argumentos recebidos sejam números, antes de tentar realizar uma soma destes e retornar o resultado.

É importante notar que esse controle adicional não remove a imprevisibilidade, isto é, durante a execução do programa a função poderia receber argumentos não numéricos, porém ao menos seria possível identificar a origem do problema e ao mesmo tempo impedir uma operação inválida.

Outras medidas de controle poderiam ser implementadas no lugar da interrupção, como tentar realizar uma conversão de tipo ou assumir zero, caso isso não fosse possível, sem que o programa precisasse ser interrompido. Novamente, aí dependerá do objetivo e das premissas envolvidas.

Múltiplos retornos

Outro ponto que diferencia as duas linguagens está na quantidade de retornos possíveis de uma função. Em C, espera-se um ou nenhum retorno (quanto o tipo da função é void). Porém não é possível retornar mais de um valor em uma função, situação que geralmente é contornada fazendo uso de estruturas ou ponteiros.

Por outro lado, em Lua, uma função pode retornar múltiplos valores:

function anteriores(num)
    return num-1, num-2, num-3
end

a, b, c = anteriores(4) --> a = 3, b = 2, c = 1

Funções como valores de primeira classe

Uma característica muito importante das funções em Lua é o fato de que elas são um valor de primeira classe, ou seja, são tratadas como um valor como qualquer outro, que pode ser armazenado em uma variável ou passado como parâmetro para outra função.

Vejamos esse conceito com um exemplo bem simples:

function soma(a,b)
    return a + b
end

mais = soma

soma(3,4) --> 7
mais(3,4) --> 7

O que aconteceu neste trecho? À variável mais foi atribuído o valor soma, que por sua vez é uma função, logo isso significa que agora tanto soma quanto mais são uma função. A mesma função! A partir desse momento, o uso das duas é intercambiável sem qualquer efeito colateral.

Como isto é possível? Lembra-se de que em Lua os tipos são associados aos valores e não às variáveis? Bem, ocorre que na prática, ao declarar uma função, não estamos fazendo mais do que declarar uma variável cujo valor será uma função.

As duas linhas a seguir produzem exatamente o mesmo efeito:

function soma(a,b) return a + b end
soma = function(a,b) return a + b end

Isto nos leva a uma outra conclusão importante: em Lua, funções são anônimas! O que convencionamos chamar de “nome da função” nada mais é que uma variável a partir de onde se pode acessar a função.

E não acaba aí: se funções são valores de primeira classe, atribuíveis a uma variável, então elas podem também ser argumentos para outras funções. Isto é útil para implementar funções de alta ordem, isto é, funções que terão seu comportamento modificado por uma outra função informada como argumento.

Função principal

Em C, um dos primeiros conceitos aprendidos é o da função principal (main), isto é, uma função que obrigatoriamente existe em todo programa escrito em C, que será o ponto de entrada do programa. Esta função pode assumir duas assinaturas, uma na qual o programa não recebe argumentos, e outra na qual o programa receberá uma lista de argumentos.

int main(void)
{
    /* o programa não recebe argumentos */
}

int main(int argc, char **argv)
{
    /* o programa recebe argumentos, sendo:
     * argc = o número de argumentos recebidos + 1
     *        (pois o primeiro argumento é o próprio programa)
     * argv = uma lista de cadeias de caracteres, na qual cada
     *        cadeia é um argumento
     * (argv também pode aparecer na forma de *argv[])
     */
}

Em outras palavras, um programa C possui uma função que delimita as suas entradas e saídas (isto é, os argumentos, quando aplicáveis, e o código de retorno) e que coordena todas as demais ações do programa, como atribuições de variáveis e chamadas a outras funções, por exemplo.

Em Lua, por outro lado, não há o conceito de função principal. Isto ocorre porque Lua é na realidade uma biblioteca, de modo que invariavelmente a função principal estará em um programa hospedeiro (ainda que esse programa seja o interpretador Lua). Todo código Lua será chamado por esse programa hospedeiro.

Estruturas de controle

Estruturas de controle alteram o fluxo de execução do programa, determinando que ele siga um caminho ou outro, sem necessariamente obedecer a ordem das linhas de código do início ao fim.

Assim, temos estruturas condicionais, que determinam se um trecho de código será executado ou não com base em uma condição, estruturas de repetição, que repetem a execução de algumas instruções, possivelmente alterando alguns parâmetros entre uma execução e outra, e alguns mecanismos mais exóticos, como goto que transfere o fluxo para locais especificados dentro de uma mesma função.

Em Lua temos ainda um recurso muito poderoso, que são as co-rotinas, as quais permitem criar fluxos de execução que colaboram entre si, separando o ciclo de vida de cada um deles, porém com a possibilidade de intercâmbio de dados entre eles.

Nas subseções a seguir discutiremos as diferenças entre as estruturas de controle presentes em C e Lua.

Estruturas condicionais

Estruturas condicionais em Lua são até bem parecidas com aquelas usadas em C. No entanto é bom ressaltar que Lua não conta com instruções como switch em C (logo mais veremos por quê isso não é necessário em Lua).

Vejamos um exemplo básico escrito em C, com um equivalente em Lua.

Em C:

if (x == 1)
{
    printf("x é igual a 1\n");
} else if (x == 0)
{
    printf("x é igual a 0\n");
} else
{
    printf("x é algum outro valor\n");
}

Em Lua:

if x == 1 then
    print "x é igual a 1"
elseif x == 0 then
    print "x é igual a 0"
else
    print "x é algum outro valor"
end

Como podemos observar, as principais diferenças em Lua são a não necessidade de usar parênteses para as condições, a delimitação por meio de palavras reservadas no lugar das chaves, e a presença da palavra reservada elseif, que equivale ao encadeamento de um bloco else com outro bloco if em C.

Voltemos a falar da instrução switch. Em C ela permite definir ações específicas para valores previstos, evitando assim que uma longa cadeia de vários blocos condicionais precise ser construída. Por exemplo:

switch (silabas) {
    case 1:
        printf("Monossílaba\n");
        break;
    case 2:
        printf("Dissílaba\n");
        break;
    case 3:
        printf("Trissílaba\n");
        break;
    default:
        printf("Polissílaba\n");
        break;
}

Em Lua, poderíamos obter o mesmo efeito com uma combinação de uma tabela e funções (que inclusive podem ser anônimas):

local classificacao = {

    -- índices 1, 2 e 3 são funções anônimas
    function() print "Monossílaba" end,
    function() print "Dissílaba" end,
    function() print "Trissílaba" end,

    -- o índice default será usado para demais valores
    default = function() print "Polissílaba" end
}

if not classificacao[silabas] then
    silabas = "default"
end

classificacao[silabas]()

Neste caso, os três primeiros valores da tabela correspondem, respectivamente aos índices 1, 2 e 3. Caso algum valor diferente seja informado (incluindo nil, então a função reservada para o valor “default” será chamada. Note que aqui usei o nome “default” apenas por convenção, mas poderia ser qualquer nome, na realidade.

Esse exemplo usa funções muito simples, então apenas por fins didáticos foi feito de modo correspondente ao exemplo anterior em C, porém este cenário específico poderia ser ainda melhor formulado em Lua como a seguir:

local classificacao = {
    "Monossílaba", "Dissílaba", "Trissílaba",
    default = "Polissílaba"
}

print(classificacao[silabas] or classificacao.default)

Ainda outra reformulação (mais elegante, na minha opinião), usando uma função, seria:

local function classificacao(num)
    return ({
        "Monossílaba", "Dissílaba", "Trissílaba"
    })[num] or "Polissílaba"
end

print(classificacao(silabas))

Dentro da função poderíamos ainda aplicar algumas validações como garantir que se trabalhe com um valor numérico e que ele seja um inteiro positivo, por exemplo. Depois, o uso da função será extremamente simples, sem necessidade de realizar essas verificações novamente.

Estruturas de repetição

Na maioria das linguagens de programação as estruturas de repetição assumem três formas possíveis. A depender da forma, um trecho de código será repetido:

  1. Enquanto uma condição for verificada como verdadeira;
  2. Para um intervalo pré-determinado;
  3. Até que uma condição seja considerada falsa, executando ao menos uma vez.

Tanto para C como para Lua, a primeira forma é materializada pelo laço iterativo while (isto é, enquanto, em inglês).

Em C:

while (condicao)
{
    /* Trecho de código a ser repetido */
}

Em Lua:

while condicao do
    -- Trecho de código a ser repetido
end

Novamente, percebemos que Lua usa em sua sintaxe palavras reservadas como delimitadores do bloco, diferentemente de C, que usa chaves. No entanto, a estrutura geral é basicamente a mesma. Laços iterativos desse tipo são comuns para construção de repetições “infinitas” (isto é, repetições onde apenas fatores externos, fora do trecho de código do laço determinarão o seu fim).

Em C, temos a possibilidade de usar os comandos break ou continue, para encerrar o laço ou a iteração atual, respectivamente.

while (condicao)
{
    /* ... */

    if (encerrar)
    {
        break;
    }

    if (proximo)
    {
        continue;
    }

    /* ... */
}

Evidentemente, não faz muito sentido usar o comando continue no fim do laço, e nem o comando break atrelado a uma condição que se espera determinar a última iteração do laço. O mesmo pode ser dito de usar esses comandos de maneira determinista, ou seja, fora de um bloco condicional, pois isso anularia a capacidade de repetição.

Muito bem, mas e em Lua? Em Lua não temos a instrução continue, apenas break. Logo mais veremos que isso pode ser contornado.

Para todos os efeitos, vejamos um exemplo de break em Lua:

while condicao do
    -- ...

    if encerrar then
        break
    end

    -- ...
end

Mais uma vez, estruturalmente bem parecido com C. Vamos então discutir o segundo tipo de laço iterativo, o laço for, e aqui começamos a observar uma diferença maior entre as duas linguagens. Para início de conversa, as primeiras versões de Lua nem mesmo possuíam esse tipo de laço, pois era (e é) possível construí-lo apenas com o uso de funções recursivas.

Ainda assim, posteriormente esse recurso foi adicionado a Lua, embora com um destaque a ser feito: existem duas formas de laço for em Lua, um numérico (semelhante ao de C), e outro genérico (isto é, iterando por um conjunto de valores informado como referência). Vamos recapitular primeiro como funciona o laço for em C:

for (int i = 0; i < 10; i++)
{
    vetor[i] = i * 2;
}

Neste exemplo simplório, repetimos o trecho em questão por uma faixa de valores que vai de 0 a 9, de modo que esse valor é utilizado em cada uma das iterações. Assim, cada índice do vetor guarda um valor que é o seu dobro, neste exemplo.

Perceba que temos, entre parênteses, três passos, separados entre ponto-e-vírgula: a definição de um valor inicial, a condição para que a iteração continue, e um passo de incremento, que eventualmente fará com que o segundo passo em algum momento retorne como falso.

Em Lua, teríamos o mesmo efeito com o trecho a seguir:

for i=0,9 do
    vetor[i] = i * 2
end

Neste caso, utilizamos dois parâmetros de iteração, o valor inicial (0), e o valor final (9), tendo como implícito que o passo de incremento (que em C deve ser sempre explícito), que se some 1 ao valor de i. Caso quiséssemos a ordem reversa, teríamos que explicitar o passo de incremento (que na verdade seria um decremento):

for i=9,0,-1 do
    vetor[i] = i * 2
end

Como disse, além deste tipo de construção, o laço for também assume o formato genérico, no qual informamos uma lista pela qual ele itera. Essa lista, muitas vezes é extraída de uma tabela, com auxílio das funções ipairs (para índices numéricos apenas, ordenado), e pairs (para índices numéricos ou não, sem uma ordem específica).

Vejamos um exemplo com ipairs:

local t = { 10, 20, 30 }
for i, v in ipairs(t) do
    t[i] = v + (t[i-1] or 0)
end

-- agora t possui { 10, 30, 60 }

Note que neste exemplo, utilizamos duas variáveis. A primeira (i) guarda o índice de cada item consultado, enquanto a segunda (v) guarda o valor do item correspondente ao índice. O valor de i neste caso seguirá de 1 até o o índice correspondente ao último item da tabela (neste exemplo, 3).

Observe também que a atribuição realizada para cada iteração depende do resultado da iteração anterior. Como estamos utilizando a função ipairs neste caso, então podemos prever a ordem de execução, que será do primeiro ao último item da tabela.

Vejamos agora um exemplo com pairs:

local cor = { r = 0.5, g = 1, b = 0.3 }
for k,v in pairs(verde) do
    cor[k] = v * 255
end

Neste caso, estamos trabalhando com índices não numéricos (embora índices numéricos pudessem estar presentes também). Quando usamos a função pairs não podemos prever a ordem das execuções, portanto estabelecer instruções que dependam da iteração anterior (o que não é feito neste exemplo) é desaconselhável, e quando necessário requer maior cautela.

Não necessariamente precisamos usar ipairs ou pairs em um laço desse tipo. Podemos construir outras funções para navegar por uma lista de valores. Porém a construção de geradores já é um tópico que está um pouco além dessa discussão.

Vamos então ao terceiro tipo de laço iterativo. Em C, ele existe sob a forma do-while ao passo que em Lua ele possui uma nomenclatura diferente: repeat. Vejamos um exemplo em C:

int x = 0;

do {
    printf("%d\n", x);
    muda_valor(x);
} while (x);

Vamos abstrair o que faz a função muda_valor() e apenas supor que ela altera o valor do inteiro x, de modo que ele pode se tornar qualquer outro valor inteiro (incluindo 0, que em C conta como uma condição falsa). Neste caso, mesmo que o valor inicial dessa variável seja 0, temos a garantia de que o laço ocorrerá ao menos uma vez, e depois, caso o valor se mantenha como 0, aí sim o laço se encerra.

Podemos obter o mesmo efeito em Lua, com o laço do tipo repeat. Vejamos um exemplo:

local x = 0

repeat
    print(x)
    muda_valor(x)
until x == 0

Há duas diferenças fundamentais nos exemplos C e Lua propostos aqui:

  1. Em C, 0 conta como uma condição de falsidade, e em Lua 0 é um valor verdadeiro;
  2. Na última linha do laço do-while de C, usamos uma condição que enquanto verdadeira mantém o laço em execução, ao passo que na última linha do laço repeat de Lua, usamos uma condição que enquanto falsa mantém o laço em execução.

A despeito dessas diferenças, os dois exemplos cumprem o mesmo papel, isto é, executar um laço cuja primeira execução é garantida, e que depois deve ser constantemente reavaliada.

Cabe destacar mais um aspecto do comando break de Lua, que tanto para laços while, também pode igualmente ser usado para os laços for (tanto o numérico como o genérico) e repeat, assim como ocorre em C.

Por fim, precisamos falar sobre o comando return, que é usado para retornar de uma função, passando um valor de retorno ou não (em Lua, como vimos, podendo passar até mais de um valor). Esse comando também é usado muitas vezes para encerrar um laço prematuramente, pois ao encerrar a função, encerra junto o laço contido nela.

Isso pode ser observado tanto em C, como em Lua. No entanto, um destaque precisa ser feito: em Lua, o comando return precisa ser o último comando do bloco em que se encontra. Caso contrário, um erro será retornado durante a definição da função (ou execução do arquivo, já que um arquivo de script Lua também é uma função em Lua).

Portanto, o seguinte trecho emitirá um erro:

function errado()
    return -- este return não é
           -- o último comando do bloco

    print "Esta função está errada"
end

Isto é assim pois não faz sentido definir algo para ser feito em um escopo no qual não se espera que a função esteja em execução. No entanto, se por algum motivo esse trecho posterior ao return puder ser executado, uma forma de contornar o erro seria criando um bloco explícito:

function sem_erro()
    do          -- bloco interno criado
        return  -- último comando do bloco
    end         -- fim do bloco interno

    print "Esta função não possui erros sintáticos."
end

Quando esse tipo de construção poderia fazer sentido? Bem, se pudéssemos saltar de um ponto da função direto para o final, sem passar por essa instrução de retorno, então faria sentido. E ocorre que podemos fazer isso, (usando goto)! Siga com a leitura e veja como.

goto

Muito bem, passamos pelos mecanismos de controle mais convencionais. Vamos agora discutir um que é alvo de muita controvérsia (especialmente em C), e que funciona de modo muito semelhante em C e em Lua. Vale destacar que em Lua esse recurso existe a partir da versão 5.2.

A instrução goto permite que realizemos saltos dentro de uma função, para um ponto previamente demarcado como possível local de destino. Tais locais são chamados de labels (rótulos).

Note que eu disse dentro de uma função, isto é, não é possível saltar para rótulos fora da função onde se está. Isso vale tanto para C como para Lua. Em Lua, inclusive, como funções são valores de primeira classe e podem ser definidas dentro de outras funções, é necessário dizer que também não é possível saltar para o escopo de uma função interna à função atual.

Saltos com goto podem servir a diversos propósitos, como por exemplo, após um erro no programa, saltar para um ponto em que os recursos alocados até então sejam liberados (como blocos de memória alocados, arquivos abertos ou conexões abertas com serviços de rede), bem como simular o comportamento de laços iterativos ou de chamadas recursivas.

A seguir temos um simplório programa C que utiliza o comando goto para implementar o comportamento de um laço do tipo do-while, isto é, que executa ao menos uma vez. Nele, utilizamos dois rótulos (inicio e fim), entre os quais se encontra a lógica que poderá ou não se repetir.

int main(void)
{
    char *linha = NULL;
    size_t limite = 0;
    ssize_t recebido;
    int capturado;
    float num;

inicio:

    printf("Informar número: ");
    fflush(stdout);

    recebido = getline(&linha, &limite, stdin);
    capturado = sscanf(linha, "%g", &num);

    if (recebido == -1 || capturado == 0) { goto fim; }

    printf("Número informado: %g\n", num);
    goto inicio;

fim:
    free(linha);
    printf("Número inválido. Programa encerrado.\n");
    return EXIT_SUCCESS;
}

Como se pode ver, o programa continuamente captura uma entrada informada pelo usuário, na qual se espera encontrar um número. Caso seja encontrado, o programa realiza um salto para o ponto de início e repete esse processo. Caso não, ele pula para o fim, informa que não obteve um número válido, e encerra o programa.

Em Lua, podemos implementar o mesmo programa conforme a seguir:

local num

:: inicio ::

io.write "Informar número: "
io.flush(io.stdout)

num = io.read "n"

if not num then goto fim end

io.write(("Número informado: %g\n"):format(num))
goto inicio

:: fim ::

print "Número inválido. Programa encerrado."

Note que não utilizamos explicitamente uma função nesse exemplo, porém vale lembrar que todo trecho Lua em última análise é uma função, e portanto o uso de goto dentro de um mesmo arquivo-fonte conta como o uso dentro de uma função.

Lembra-se quando comentei, na seção anterior, que Lua não possui a instrução continue para encerrar a iteração atual dentro de um laço e passar para a próxima? Pois bem, goto ao resgate:

-- suponha que temos uma matriz tridimensional m
-- vamos varrer seu conteúdo com três laços aninhados

for _,x in ipairs(m) do
    for _,y in ipairs(x) do
        for i,z in ipairs(y) do
            
            if encerra_z then goto interno end
            if encerra_y then goto meio end
            if encerra_x then goto externo end

            if encerra_tudo then goto sair end

            io.write(z, " ")

            :: interno :: -- saltar para fim do laço
                          -- interno, equivalente a
                          -- continue (que não existe
                          -- em Lua)
        end
        :: meio :: -- saltar para fim do laço
                   -- intermediário, equivalente a um
                   -- comando break executado no laço
                   -- mais interno

        io.write "\n"
    end
    :: externo :: -- saltar para o laço mais externo,
                  -- possível apenas com goto
end

:: sair :: -- sair de todos os laços, também possível
           -- apenas com goto

Veja que nesse exemplo, definimos rótulos em locais estratégicos, para encerramento de cada um dos laços. O do meio não é estritamente necessário, pois para ele poderíamos usar break com o mesmo efeito.

Temos então três condições hipotéticas, para interrupção de uma iteração específica de cada um dos laços, e uma para encerrar tudo e sair de todos os laços. Veja então que além de permitir a implementação da funcionalidade equivalente a continue, como bônus o comando goto também pode ser usado para sair de laços mais externos.

Co-rotinas

Já que estamos falando de mecanismos de controle, seria imprudente não abordar um tema muito importante em Lua, que são as co-rotinas. Trata-se de um conceito bastante antigo e que ao longo de décadas ganhou diferentes formas, cada qual com suas peculiaridades, e que em Lua surgiu na versão 5.0.

Infelizmente em C não há um mecanismo nativo para implementação de co-rotinas, o que dificulta um pouco o nosso comparativo, mas falar sobre esse tema será importante justamente para que possamos visualizar algumas situações em que Lua complementa C, facilitando o acesso a mecanismos dessa natureza.

Dito isso, vamos ao que interessa. O que são co-rotinas? Como disse, há variações desse mecanismo, mas em essência, uma co-rotina é um fluxo de execução que é independente do fluxo principal do programa, e que em algum ponto desse fluxo principal pode ser acionado, quase como se fosse uma chamada de função.

Acionar uma co-rotina transfere o controle do programa do fluxo principal para o fluxo dessa co-rotina, que seguirá suas instruções em ordem até que seja encerrado (novamente, igual uma função) ou seja suspenso, devolvendo o controle para o fluxo principal ou ainda delegando para uma outra co-rotina.

Ao ser suspensa, uma co-rotina não é encerrada, e o controle pode ser passado a ela novamente depois, diferente do que pode ser feito em uma função. Ao recuperar o controle, ela prosseguirá do ponto em que parou, e terá guardados os mesmos valores em suas variáveis, no momento em que foi suspensa, como se tivesse sido congelada no passado, e então descongelada para prosseguir.

Para melhor ilustrar, pensemos em um filme. Ao assistir o filme, ele segue uma linha cronológica no qual os fatos vão acontecendo, e mantemos nossa atenção nesse fluxo. Porém, podemos em algum momento pausar a exibição e ir fazer outra coisa.

Em momento posterior, voltamos para continuar, e podemos seguir do ponto em que ele parou, sem necessidade de iniciar tudo outra vez. Podemos pausar e voltar novamente outras vezes, até que o filme termine (o que equivale ao fim do fluxo da co-rotina), ponto em que o controle é devolvido ao fluxo principal, o que neste exemplo corresponde ao seu cotidiano.

Certo, agora com o conceito esclarecido, vamos ver na prática com o que uma co-rotina se parece. A seguir temos um exemplo simples de co-rotina em Lua. Não se preocupe se não entender tudo, vamos dissecar essa estrutura.

-- variável "co" a seguir guarda um fluxo
-- (ou seja, tipo thread)

local co = coroutine.create (
    -- função que corresponde ao corpo do fluxo:
    function(x)
        x = coroutine.yield(x + 10) -- fluxo suspenso
        return x + 10               -- fim do fluxo
    end
)

local x = 0

repeat
    -- Verificar estado da co-rotina
    print("Estado", coroutine.status(co))

    io.write(("x = %d\n"):format(x))
    x = x + 20

    -- Iniciar/retomar co-rotina, passando x,
    -- e obtendo y como retorno
    continuar, x = coroutine.resume(co, x)
until continuar == false

Neste exemplo, criamos uma co-rotina usando a função create da biblioteca padrão de Lua, para manipulação de co-rotinas (coroutine). O conjunto de instruções a ser executado pela co-rotina é encapsulado por uma função.

Entre o início e o retorno da função, temos um ponto em que o seu fluxo é suspenso, devolvendo x + 10 como valor de retorno ao fluxo principal. Este, por sua vez, exibe o valor na tela, aumenta em 20 e passa o resultado novamente para a co-rotina, que então soma 10 e retorna, encerrando.

Note o uso da variável continuar, que recebe um valor verdadeiro ou falso, a depender do estado da co-rotina. Se ela ainda estiver suspensa, significa que ainda poderá ser retomada novamente. Porém, se estiver encerrada, não poderá, portanto continuar será um valor falso.

No início do laço repeat verificamos o estado da co-rotina criada. Vejamos qual seria a saída na tela ao executar este programa:

Estado  suspended
x = 0
Estado  suspended
x = 30
Estado  dead
x = 60

Observe que os dois primeiros estados informados são suspended (suspenso), enquanto o último é dead (morto), indicando que o fluxo da co-rotina já retornou e foi encerrado.

Em Lua, as co-rotinas possuem dois mecanismos de controle, um para acionamento (resume), que é chamado de fora da co-rotina para transferir o fluxo para ela, e outro para suspensão (yield), chamado de dentro da co-rotina, para devolver o controle ao fluxo que a iniciou. Por funcionar nessa dinâmica, as co-rotinas em Lua são chamadas de assimétricas.

Além disso, em Lua as co-rotinas são tratadas como valores de primeira classe, materializados sob o tipo thread (fio ou segmento). Isso significa que co-rotinas podem ser passadas como parâmetros em funções e armazenadas em variáveis. Como os demais tipos mutáveis, estão sujeitos à coleta de lixo, quando não mais utilizados.

Vale destacar o fato de que apesar desse nome (thread) não se trata de um conceito homônimo usado em sistemas operacionais para processos executados simultaneamente. Diferente destes, as co-rotinas são mais leves, por não envolver bloqueios ou chamadas de sistema, e são portáveis, isto é, não dependem de um suporte específico do sistema operacional.

É importante notar também o fato de que ao ser iniciada ou retomada, uma co-rotina pode receber parâmetros, e ao ser suspensa ou encerrada, ela pode devolver um ou mais valores de retorno, e isso permite uma comunicação entre diferentes fluxos.

É óbvio que o exemplo que mostrei é completamente inútil, porém co-rotinas são um conceito às vezes difícil de absorver, e por isso achei, pelo bem da didática, melhor avançar aos poucos. Mas vejamos uma pequena mudança que poderíamos ter feito no exemplo anterior:

local co = coroutine.create(function(x)
    -- laço infinito:
    while 1 do
        x = coroutine.yield(x + 10)
    end
end)

Neste caso, teríamos uma co-rotina que nunca acaba, mas que pode ser suspensa indefinidas vezes, e a cada vez chamada, ela deverá receber um número e o devolverá incrementado em 10. Poderíamos aplicar essa mesma lógica para diversas outras operações mais complexas que esse incremento.

Ainda outro aspecto importante a se mencionar é o de que em Lua co-rotinas podem ser suspensas ou retomadas mesmo de dentro de chamadas aninhadas, isto é, cada fluxo de execução possui sua própria pilha de chamadas, que é preservada ao transferir o controle para outro fluxo. Vejamos o que isso significa na prática.

local function acionar(co)
    coroutine.resume(co)
end

local function suspender()
    print "3. Suspendendo co-rotina"
    coroutine.yield()
    print "5. Co-rotina retomada"
end

local co = coroutine.create (
    function()
        print "2. Iniciando co-rotina"
        suspender()       -- suspensão indireta,
                          -- via função suspender
        print "6. Encerrar co-rotina"
    end
)

print "1. Iniciar co-rotina"
acionar(co) -- acionamento indireto,
            -- via função acionar

print "4. Retomar co-rotina"
acionar(co)

print "7. Co-rotina encerrada"

Ao executar este programa veremos a seguinte saída:

1. Iniciar co-rotina
2. Iniciando co-rotina
3. Suspender co-rotina
4. Retomar co-rotina
5. Co-rotina retomada
6. Encerrar co-rotina
7. Co-rotina encerrada

Observe como o controle é devolvido para a função suspender quando a co-rotina é retomada, e não diretamente para o corpo principal da co-rotina, o que mostra que a pilha de chamadas é preservada durante a suspensão.

A esta altura você já deve ter percebido que co-rotinas são um mecanismo bastante poderoso e versátil, útil para construir geradores, tratar exceções, controlar o movimento de diferentes atores em um jogo, coordenar fluxos de produção e consumo em uma fila, dentre várias outras situações.

Porém, ao mesmo tempo talvez esteja pensando algo como: “Tudo bem, isso é muito legal, mas qual é a relação disso com C?”. Afinal, já mencionei que C não possui mecanismo semelhante. Diante disso, explico. A questão é que co-rotinas são um recurso que pode ser igualmente útil em um programa escrito em C, então como poderíamos obter algo semelhante?

A bem da verdade, não é impossível, e existem algumas maneiras de implementar co-rotinas em C. Algumas são dependentes da arquitetura de processamento ou do sistema operacional, outras são um tanto deselegantes, pra dizer o mínimo, e requerem muito cuidado ao serem utilizadas.

Mas o fato é que existem bibliotecas que implementam co-rotinas, e lembremos: Lua não é apenas uma linguagem, mas também uma biblioteca. Por acaso, uma biblioteca escrita em C, com uma API C, e que implementa co-rotinas. Logo, usar a API C de Lua é uma opção para obter essa funcionalidade em um programa escrito em C.

E com um bônus: podemos criar co-rotinas escritas em C ou em Lua, e interagir com elas a partir da parte escrita em C. É claro que usar co-rotinas por meio da API C será um pouco menos trivial do que fazê-lo em Lua, e é preciso se familiarizar com a API C (a qual não caberá no escopo deste documento, que é um comparativo das linguagens), mas o ponto é que é uma opção viável.

Modularização

Escolher nomes para variáveis e/ou funções pode ser uma tarefa trivial para programas pequenos. Porém, à medida que os programas crescem, essa escolha começa a impor algumas dificuldades e exigir uma maior disciplina com regras de nomenclatura. Afinal, é importante escolher nomes que transmitam significado e ao mesmo tempo não entrem em conflito com outros nomes, usados para fins diferentes.

O modo como se lida com isso quase sempre envolve, em alguma medida, um alinhamento com as regras de escopo. Variáveis de escopos menores geralmente possuem nomes mais curtos, que facilmente poderiam ser encontrados em outras partes do programa para finalidades diferentes. Como se trata de um escopo pequeno, isso não causa problemas. Variáveis de escopos maiores (globais, por exemplo) são usadas com mais cautela e possuem nomes mais longos e mais difíceis de se confundir com outras.

Até aqui, não há grande diferença entre C e Lua. Porém, em algumas linguagens é comum dar um passo além nesse sentido, criando regras para confinar variáveis a um espaço de nomes, de modo que variáveis criadas em um espaço não entram em conflito com variáveis criadas em outro, mesmo que ambas possam ser usadas em um mesmo escopo dentro do programa.

Como vetores associativos que são, as tabelas em Lua podem ser usadas para cumprir esse papel, e não por acaso o suporte a módulos em Lua é construído sobre tabelas. Módulos não apenas ajudam a separar os espaços de nomes, mas também a manter o código organizado e mais fácil de reaproveitar.

Em C, por outro lado, é no mínimo incomum o uso do termo “módulo”. Tradicionalmente, o código em C é agrupado em bibliotecas, que são importadas em arquivos-fonte C. Nesse sentido, bibliotecas seriam a menor forma de agrupamento de código em C, o que é um incentivo para que sejam mais enxutas.

Pode-se argumentar que bibliotecas e módulos cumprem a mesma função, e em algum nível isso é verdadeiro, isto é, agrupam código relacionado que pode ser reaproveitado em outros programas. No entanto, módulos separam os espaços de nomes, ao passo que bibliotecas não o fazem. Vejamos um exemplo para ilustrar, considere o seguinte trecho de código Lua:

local groselha = require "groselha"
local tamarindo = require "tamarindo"

local suco1 = groselha.suco()
local suco2 = tamarindo.suco()

Podemos observar nesse exemplo que os módulos groselha e tamarindo são importados no início do programa, e que a função suco() está presente em ambos. Perceba que as duas funções, a despeito de terem o mesmo nome, são usadas no mesmo programa, e dentro do mesmo escopo.

Em C, um arranjo diferente seria necessário, pois se tivéssemos duas bibliotecas exportando a mesma função, ambas importadas para um mesmo arquivo-fonte, isso resultaria em um conflito. Caso as funções tenham assinatura diferente, um erro de compilação será emitido. Caso tenham assinatura igual, o conflito ocorrerá na etapa de ligação do programa.

Por este motivo, o cuidado com os nomes de funções e variáveis exportadas por uma biblioteca C deve ser maior, e muitas vezes usam-se prefixos nos nomes para identificar a biblioteca de origem de uma variável ou função, o que reduz as chances de conflito.