Menu

Closures e funções anônimas em Golang, com exemplos

Funções anônimas em Go podem capturar variáveis do escopo ao redor. Isso gera closures: contadores, geradores, middlewares e callbacks que guardam o próprio estado.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Funções anônimas

Um literal de função é uma função sem nome. Ele é uma expressão, então pode ir em qualquer lugar onde cabe um valor: uma variável, um argumento, um valor de retorno, um campo de struct.

Go não tem atalho de arrow function ou lambda. A forma completa func(params) results { body } é o único jeito de escrever uma.

Capturando variáveis

Um literal de função pode usar variáveis da função que o envolve. Ele não as copia: a closure e o código ao redor compartilham uma única variável.

A variável capturada vive enquanto alguma closure ainda se referir a ela, mesmo depois que a função que a declarou retornou. O compilador a move para o heap quando necessário; você nunca gerencia isso.

Geradores e contadores

Devolver uma closure de uma função dá a cada função devolvida o seu próprio estado privado.

Cada chamada a counter() cria um n novo, então c1 e c2 contam de forma independente. Nada do lado de fora consegue ler ou zerar n, o que faz disso uma pequena forma de encapsulamento.

Closures em laços (Go 1.22 em diante)

Antes do Go 1.22, um laço for tinha uma única variável de laço para o laço inteiro. As closures criadas dentro dele capturavam todas essa mesma variável e viam o seu valor final. Esse era o bug de closure mais comum em Go.

Desde o Go 1.22, cada iteração ganha a sua própria cópia, então o código abaixo imprime 0 1 2:

No Go 1.21 e anteriores, o mesmo laço escrito como for i := 0; i < 3; i++ (range sobre inteiro também é novidade do 1.22) imprimia 3 3 3. A nova regra vale por módulo, com base na linha go do go.mod, então um módulo antigo continua com o comportamento antigo até que a sua linha go seja elevada para 1.22 ou mais. Você ainda vai ver i := i ou v := v dentro de laços em código antigo: era a correção manual e agora é redundante.

A regra por iteração só cobre variáveis declaradas pela própria instrução for. Uma variável declarada antes do laço e atualizada no corpo continua sendo uma única variável compartilhada:

var last string
for _, s := range items {
	last = s
	handlers = append(handlers, func() { use(last) }) // every closure sees the final value
}

Closures e goroutines

Uma goroutine iniciada com um literal de função também é uma closure. As mesmas regras de compartilhamento valem, mais a regra de sempre da concorrência: se várias goroutines escrevem em uma variável capturada, você precisa de um mutex ou de um channel.

Isto sempre imprime 5050. Cada goroutine lê o seu próprio i (semântica de laço do Go 1.22) e escreve no total compartilhado sob um lock. Tire o mutex e o resultado fica imprevisível; go run -race reporta uma data race. Veja goroutines para WaitGroup e o detector de race.

Onde closures aparecem em código real

  • Ordenação e busca: slices.SortFunc, slices.IndexFunc e sort.Slice recebem um literal de função.
  • Limpeza adiada: defer func() { ... }() executa um bloco na saída da função e pode ler as variáveis dela. Veja defer.
  • Middleware HTTP: uma função que recebe um handler e devolve um novo literal http.HandlerFunc que o envolve.
  • Configuração: functional options (func WithTimeout(d time.Duration) Option { return func(c *Config) { c.Timeout = d } }) são closures sobre o argumento.

Um exemplo de middleware que captura tanto um parâmetro quanto um contador:

Erros comuns

  • Esperar um retrato do valor. Uma closure lê o valor atual da variável quando executa, não o valor de quando foi criada. Passe o valor como parâmetro se precisar de um retrato: go func(v int) { ... }(x).
  • Literais recursivos. Um literal de função não pode se referir a si mesmo pela variável a que está sendo atribuído no mesmo :=. Declare a variável antes: var walk func(n int); walk = func(n int) { ... walk(n-1) }.
  • Escritas sem sincronização a partir de goroutines. Capturar não é sincronizar. Proteja as escritas compartilhadas.

Perguntas frequentes

O que é uma função anônima em Go?

Um literal de função sem nome: func(x int) int { return x * 2 }. Você pode atribuí-la a uma variável, passá-la como argumento, devolvê-la ou chamá-la na hora acrescentando () depois da chave de fechamento. Go não tem uma sintaxe de lambda separada; o literal de função é a lambda.

O que é uma closure em Go?

Um literal de função que se refere a variáveis declaradas fora dele. A função mantém essas variáveis vivas e as compartilha com o escopo que a envolve, então mudanças feitas dentro da closure ficam visíveis fora, e vice-versa. As variáveis são capturadas por referência, não copiadas.

Closures em Go capturam variáveis por valor ou por referência?

Por referência. A closure e o código ao redor usam a mesma variável. Se você precisa de um retrato do valor, copie-o para uma variável nova antes de criar a closure, ou passe-o como argumento.

O bug da variável de laço em closures foi corrigido no Go?

Sim, desde o Go 1.22. Cada iteração de um laço for agora declara uma variável de laço nova, então closures e goroutines criadas no laço veem o valor daquela iteração. Vale para módulos cujo go.mod diz go 1.22 ou mais recente. Código antigo muitas vezes tem i := i dentro do laço como contorno; isso não faz mal, mas não é mais necessário.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR