Closures em Go
1. O que são Closures?
Em Go, uma closure é uma função que captura e mantém referências a variáveis do escopo externo onde foi definida. Isso significa que a closure "lembra" do ambiente onde foi criada, mesmo após esse escopo externo ter terminado sua execução.
Uma closure pode ser entendida como uma função anônima com estado. Enquanto funções anônimas comuns executam uma operação isolada, closures carregam consigo um contexto que pode ser modificado e consultado ao longo do tempo.
package main
import "fmt"
func main() {
mensagem := "Olá"
// Closure que captura a variável 'mensagem'
closure := func() {
fmt.Println(mensagem) // Acessa variável do escopo externo
}
closure() // Output: Olá
}
A diferença fundamental entre uma função anônima simples e uma closure é que a closure mantém acesso às variáveis do escopo onde foi criada.
2. Sintaxe e Criação de Closures
Closures podem ser declaradas inline, atribuídas a variáveis ou retornadas de funções.
package main
import "fmt"
func main() {
// Declaração inline
func() {
fmt.Println("Executando closure inline")
}()
// Atribuição a variável
contador := 0
incrementa := func() int {
contador++
return contador
}
fmt.Println(incrementa()) // 1
fmt.Println(incrementa()) // 2
// Retorno de função
saudacao := criaSaudacao("Bom dia")
fmt.Println(saudacao("João")) // Bom dia, João!
}
func criaSaudacao(saudacao string) func(string) string {
return func(nome string) string {
return fmt.Sprintf("%s, %s!", saudacao, nome)
}
}
3. Captura de Variáveis por Referência
Closures em Go capturam variáveis por referência, não por valor. Isso significa que múltiplas closures compartilhando a mesma variável externa verão as alterações feitas por qualquer uma delas.
package main
import "fmt"
func main() {
x := 10
closure1 := func() { x += 5 }
closure2 := func() { fmt.Println("Valor de x:", x) }
closure1() // x = 15
closure2() // Valor de x: 15
}
Armadilha comum com loops:
Um erro clássico ocorre ao capturar variáveis de iteração em loops for:
package main
import "fmt"
func main() {
var funcoes []func()
// PROBLEMA: todas as closures capturam a mesma variável 'i'
for i := 0; i < 3; i++ {
funcoes = append(funcoes, func() {
fmt.Println(i)
})
}
for _, f := range funcoes {
f() // Output: 3, 3, 3 (não 0, 1, 2)
}
}
A solução é criar uma cópia local da variável dentro do loop:
for i := 0; i < 3; i++ {
i := i // Cria nova variável local
funcoes = append(funcoes, func() {
fmt.Println(i)
})
}
4. Closures como Fábricas de Funções
Closures são excelentes para criar fábricas de funções que geram comportamentos personalizados com estado interno.
package main
import "fmt"
// makeAdder retorna uma função que adiciona um valor fixo
func makeAdder(x int) func(int) int {
return func(y int) int {
return x + y
}
}
// makeCounter retorna um contador incremental
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
// Fábrica de somadores
add5 := makeAdder(5)
add10 := makeAdder(10)
fmt.Println(add5(3)) // 8
fmt.Println(add10(3)) // 13
// Fábrica de contadores
contador1 := makeCounter()
contador2 := makeCounter()
fmt.Println(contador1()) // 1
fmt.Println(contador1()) // 2
fmt.Println(contador2()) // 1 (contador independente)
fmt.Println(contador1()) // 3
}
5. Closures e Concorrência (Goroutines)
Closures são frequentemente usadas em goroutines para capturar o contexto necessário.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
// Problema: variável compartilhada em goroutines
for i := 1; i <= 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Processando", i) // Pode imprimir valores inesperados
}()
}
wg.Wait()
}
Solução com cópia local:
for i := 1; i <= 3; i++ {
wg.Add(1)
id := i // Cópia local
go func() {
defer wg.Done()
fmt.Println("Processando", id) // Correto: 1, 2, 3
}()
}
wg.Wait()
6. Closures em Retornos de Funções e Callbacks
Closures são fundamentais para implementar funções de alta ordem e middlewares.
package main
import (
"fmt"
"net/http"
)
// Middleware de logging com closure
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("[LOG] %s %s\n", r.Method, r.URL.Path)
next(w, r) // Chama o próximo handler
}
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Olá, mundo!")
}
func main() {
handler := loggingMiddleware(helloHandler)
// Simulação de requisição
req, _ := http.NewRequest("GET", "/hello", nil)
handler(nil, req) // Output: [LOG] GET /hello
}
7. Boas Práticas e Performance
Evitar captura desnecessária de grandes variáveis:
// Ruim: captura array grande desnecessariamente
func processarGrandeArray() func() {
dados := make([]byte, 1024*1024) // 1MB
return func() {
fmt.Println(len(dados)) // Mantém referência ao array inteiro
}
}
// Melhor: passar apenas o necessário
func processarTamanho(tamanho int) func() {
return func() {
fmt.Println(tamanho)
}
}
Impacto na alocação de memória:
Closures que capturam variáveis do escopo externo fazem com que essas variáveis sejam alocadas no heap em vez da stack, o que pode impactar a performance do garbage collector.
// Esta closure força 'contador' a ir para o heap
func criaContador() func() int {
contador := 0
return func() int {
contador++
return contador
}
}
Quando usar closures vs funções nomeadas vs métodos:
| Situação | Recomendação |
|---|---|
| Comportamento simples e único | Closure inline |
| Lógica reutilizável | Função nomeada |
| Estado associado a um tipo | Método |
| Fábrica de funções com estado | Closure retornada |
Resumo das boas práticas:
- Capture apenas as variáveis necessárias
- Faça cópias locais de variáveis em loops
- Evite closures que modifiquem variáveis externas em goroutines
- Prefira funções nomeadas para lógica complexa
- Documente o comportamento de captura de variáveis
Referências
- Documentação Oficial: Funções Anônimas em Go — Tutorial interativo da Tour of Go explicando function literals e closures
- Go by Example: Closures — Exemplos práticos e concisos de closures em Go
- Effective Go: Closures — Seção oficial de Effective Go sobre closures e boas práticas
- Dave Cheney: Closures and Goroutines — Artigo técnico sobre performance em Go, incluindo impacto de closures
- The Go Blog: Defer, Panic, and Recover — Artigo oficial que demonstra uso avançado de closures com defer
- Golang Spec: Function Literals — Especificação oficial da linguagem sobre function literals e closures
- Go Wiki: Learn Go Concurrency — Guia de concorrência com exemplos de closures em goroutines