Funções: múltiplos retornos e named returns
1. Sintaxe Básica de Múltiplos Retornos
Em Go, uma função pode retornar múltiplos valores. A declaração é feita na assinatura da função, listando os tipos entre parênteses:
func dividir(a, b int) (int, int) {
quociente := a / b
resto := a % b
return quociente, resto
}
Para capturar os retornos, usamos múltiplas variáveis separadas por vírgula:
q, r := dividir(10, 3)
fmt.Printf("Quociente: %d, Resto: %d\n", q, r) // Quociente: 3, Resto: 1
Quando não precisamos de todos os retornos, utilizamos o blank identifier (_):
q, _ := dividir(10, 3)
fmt.Println("Apenas quociente:", q)
2. Casos de Uso Comuns
O padrão mais difundido em Go é retornar valor + erro:
func lerArquivo(nome string) (string, error) {
dados, err := os.ReadFile(nome)
if err != nil {
return "", err
}
return string(dados), nil
}
Outro caso comum é retornar resultados parciais com status:
func buscarUsuario(id int) (Usuario, bool) {
for _, u := range usuarios {
if u.ID == id {
return u, true
}
}
return Usuario{}, false
}
Operações de busca frequentemente retornam valor e flag de existência:
func primeiroPar(numeros []int) (int, bool) {
for _, n := range numeros {
if n%2 == 0 {
return n, true
}
}
return 0, false
}
3. Named Returns: Declaração e Escopo
Named returns permitem nomear os valores de retorno na própria assinatura:
func dividirNamed(a, b int) (quociente, resto int) {
quociente = a / b
resto = a % b
return
}
As variáveis nomeadas são inicializadas automaticamente com seus valores zero:
func valoresZero() (x int, s string, b bool) {
// x = 0, s = "", b = false
return // retorna 0, "", false
}
O escopo dessas variáveis é o corpo inteiro da função, funcionando como variáveis locais declaradas no início.
4. Comportamento do return com Named Returns
O return sem argumentos (bare return) retorna os valores atuais das variáveis nomeadas:
func calcular(a, b int) (soma, produto int) {
soma = a + b
produto = a * b
return // equivalente a return soma, produto
}
Podemos misturar atribuições explícitas com bare returns:
func processar(valor int) (dobro, metade int) {
dobro = valor * 2
if valor == 0 {
return // retorna 0, 0
}
metade = valor / 2
return
}
É importante notar que o bare return sempre reflete o estado atual das variáveis nomeadas no momento da execução.
5. Comparação: Retornos Nomeados vs. Anônimos
Named returns melhoram a legibilidade ao documentar implicitamente o significado dos retornos:
// Sem nome - precisa ler o corpo
func parse(data string) (string, int, error)
// Com nome - auto-documentado
func parse(data string) (nome string, idade int, err error)
No stack trace e depuração, named returns aparecem com seus nomes, facilitando a identificação:
func exemplo() (resultado string, err error) {
// Em caso de panic, o stack trace mostra "resultado" e "err"
}
Quanto à performance, não há overhead — named returns são puramente uma convenção em tempo de compilação, sem custo em tempo de execução.
6. Armadilhas e Boas Práticas
Bare returns são concisos, mas devem ser usados com moderação:
// RUIM: função longa com bare return confuso
func processarDados(dados []byte) (resultado string, err error) {
if len(dados) == 0 {
err = errors.New("dados vazios")
return // o que retorna? resultado vazio e erro
}
// ... 50 linhas de código ...
return
}
// BOM: função curta com bare return claro
func soma(a, b int) (total int) {
total = a + b
return
}
Shadowing de variáveis nomeadas pode causar bugs sutis:
func exemplo() (valor int) {
valor = 10
valor := 20 // shadowing! Cria nova variável local
return // retorna 10, não 20
}
Evite uso excessivo em funções longas — prefira retornos explícitos quando a função ultrapassa 10-15 linhas.
7. Exemplos Práticos e Anti-Padrões
Divisão com quociente e resto:
// Versão sem named returns
func dividirAnonimo(a, b int) (int, int) {
return a / b, a % b
}
// Versão com named returns
func dividirNomeado(a, b int) (quociente, resto int) {
quociente = a / b
resto = a % b
return
}
Parse de dados com múltiplos retornos e erros:
func parseConfig(linha string) (chave, valor string, err error) {
partes := strings.SplitN(linha, "=", 2)
if len(partes) != 2 {
err = fmt.Errorf("formato inválido: %s", linha)
return // retorna "", "", erro
}
chave = strings.TrimSpace(partes[0])
valor = strings.TrimSpace(partes[1])
if chave == "" {
err = errors.New("chave vazia")
return
}
return // retorna chave, valor, nil
}
Anti-padrão: named returns em funções complexas:
func processarPedido(pedido Pedido) (total float64, desconto float64, frete float64, err error) {
// 30 linhas de validação
// 20 linhas de cálculo
// 15 linhas de aplicação de regras
// return implícito no final - difícil de rastrear
}
Neste caso, prefira retornos explícitos ou até mesmo uma struct de resultado.
Referências
- Effective Go: Multiple returns — Documentação oficial explicando o padrão de múltiplos retornos e seu uso idiomático em Go.
- Go by Example: Multiple Return Values — Tutoriais práticos com exemplos de código sobre retornos múltiplos.
- The Go Blog: Error handling and Go — Artigo oficial sobre tratamento de erros usando o padrão valor + erro.
- Golang Spec: Function types — Especificação completa da linguagem sobre tipos de função e parâmetros de retorno.
- Dave Cheney: Go's named return values are a code smell — Análise crítica sobre quando evitar named returns e boas práticas.
- Golangbot: Named Return Values — Tutorial detalhado com exemplos sobre named returns e bare returns.
- Stack Overflow: When to use named return values in Go — Discussão da comunidade sobre casos de uso apropriados para named returns.