Pular para o conteúdo principal
Closures e funções anônimas

Closures e funções anônimas

6 min de leitura

Arquivado emLinguagem de Programação Goem

Closures são funções que capturam variáveis do escopo que as envolve. Aprenda como funcionam em Go, por que retêm estado e onde são mais úteis.

Funções que lembram

O artigo anterior apresentou function literals — funções anônimas atribuídas a variáveis ou passadas como argumentos. Closures levam essa ideia um passo adiante: uma função que não apenas captura comportamento, mas também captura o estado no qual foi criada.

Entender closures desbloqueia um padrão que aparece por todo o Go idiomático: funções que carregam sua própria memória privada, independente de quem as chama.

Funções anônimas

Uma declaração de função nomeada vincula um nome no nível do package e é visível em qualquer lugar do package. Às vezes você precisa de um pequeno trecho de lógica que só é relevante em um único lugar — não há razão para dar-lhe um nome permanente. Uma função anônima (também chamada de function literal ou lambda) é declarada inline, sem nome:

O () ao final chama a função imediatamente após defini-la. Isso é chamado de immediately invoked function expression — útil para executar lógica de setup em um escopo restrito.

Com mais frequência, funções anônimas são atribuídas a uma variável:

double armazena um valor de função. Ele pode ser chamado, passado para outras funções ou armazenado em uma estrutura de dados — como qualquer outro valor.

Funções anônimas também são a escolha natural para callbacks: comportamento curto e específico ao contexto, passado para uma função que o chama mais tarde. A standard library usa esse padrão amplamente:

sort.Slice recebe a lógica de comparação como argumento de função. Não há razão para definir essa comparação em outro lugar — ela pertence aqui, no ponto de chamada.

O que faz uma closure

Uma função anônima se torna uma closure no momento em que referencia uma variável do escopo que a envolve. A função não recebe essa variável como parâmetro — ela fecha sobre ela, capturando uma referência à própria variável:

counter declara uma variável local count e retorna uma função anônima. Essa função retornada referencia count — uma variável que vive no escopo de counter, não no seu próprio.

Cada chamada a increment lê e modifica count. A variável persiste entre as chamadas. Mesmo que counter já tenha retornado há muito tempo e seu stack frame não exista mais, count ainda está viva — a closure a mantém viva.

Essa é a propriedade definidora de uma closure: ela retém acesso às variáveis sobre as quais fechou, mesmo depois que o escopo que as contém terminou de executar.

Closures capturam variáveis, não valores

Uma closure armazena uma referência à variável, não uma cópia do seu valor no momento em que a closure foi criada. Se a variável mudar após a criação da closure, a closure verá o valor atualizado. Essa distinção importa quando você espera que a variável capturada permaneça fixa — se precisar de um snapshot, copie-a para uma nova variável antes.

Cada closure tem seu próprio estado

Chamar counter uma segunda vez não compartilha o mesmo count. Cada chamada cria uma nova execução de counter, uma nova variável count e uma nova closure que fecha sobre essa nova variável:

a e b são completamente independentes. Cada uma é uma função pareada com seu próprio estado privado. É nesse sentido que uma closure pode ser entendida como uma função com estado associado — o estado não é global, não é passado como argumento, mas pertence à própria closure.

Closures com múltiplas operações

Uma closure pode expor mais de uma operação sobre o mesmo estado capturado. A forma mais simples é retornar múltiplas funções do mesmo escopo externo — todas compartilham a mesma variável:

Tanto push quanto pop fecham sobre o mesmo slice items. Qualquer operação modifica o estado compartilhado, e a outra vê a mudança imediatamente:

Esse padrão — agrupar operações relacionadas sobre um estado privado compartilhado — é uma alternativa leve a um struct com methods quando os dados não precisam ser expostos ou nomeados.

Memoização

Memoização é uma técnica em que uma função armazena em cache seus resultados para que chamadas repetidas com o mesmo argumento pulem o cálculo inteiramente. Uma closure é a implementação natural: o cache vive no escopo fechado, invisível para quem chama, persistindo entre as chamadas:

memoize envolve qualquer func(int) int e retorna uma nova função que armazena os resultados em cache de forma transparente. O map cache é privado — quem chama não pode inspecioná-lo nem modificá-lo:

O mesmo wrapper memoize funciona para qualquer func(int) int — a lógica do cache é escrita uma vez e o comportamento é injetado. Isso só é possível porque funções são valores de primeira classe e closures podem carregar estado privado.

Quando usar closures

Closures aparecem naturalmente em algumas situações recorrentes:

  • Callbacks — passar comportamento curto e específico ao contexto para uma função que o chama mais tarde (sort.Slice, http.HandleFunc, etc.)
  • Factories — funções que criam e retornam funções configuradas (multiplier, memoize, counter)
  • Estado encapsulado — quando você precisa de um helper com estado, mas um struct completo parece excessivo

Elas não são um substituto para structs e methods. Quando o estado é complexo, precisa de múltiplos fields ou deve ser passado pelo nome, um struct é mais claro. Use closures quando o estado é simples e o comportamento é o ponto central.