Múltiplos Retornos em Go
Go é uma das poucas linguagens modernas que suporta nativamente múltiplos retornos de funções. Diferente de linguagens como Python que retornam tuplas ou Java que exigem wrapper objects, em Go você simplesmente declara quantos valores deseja retornar e o compilador cuida do resto. Essa é uma característica fundamental que influencia toda a forma como tratamos erros e valores na linguagem.
O padrão mais comum é retornar um valor útil seguido de um erro. Isso elimina a necessidade de exceções e torna o fluxo de erro explícito no código. Quando você chama uma função que retorna múltiplos valores, é obrigado a lidar com todos eles — não é possível ignorar silenciosamente um erro. Veja como funciona na prática:
package main
import (
"fmt"
"strconv"
)
func dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("divisão por zero não permitida")
}
return a / b, nil
}
func buscarUsuario(id int) (string, int, error) {
if id <= 0 {
return "", 0, fmt.Errorf("ID inválido")
}
// Simulando busca em banco de dados
usuarios := map[int]string{1: "Alice", 2: "Bob"}
nome, existe := usuarios[id]
if !existe {
return "", 0, fmt.Errorf("usuário não encontrado")
}
return nome, id, nil
}
func main() {
// Capturando múltiplos retornos
resultado, err := dividir(10, 2)
if err != nil {
fmt.Println("Erro:", err)
} else {
fmt.Printf("Resultado: %.2f\n", resultado)
}
// Descartando retornos com blank identifier
nome, _, err := buscarUsuario(1)
if err != nil {
fmt.Println("Erro:", err)
} else {
fmt.Println("Usuário encontrado:", nome)
}
// Capturando todos os retornos
nome, id, err := buscarUsuario(2)
if err != nil {
fmt.Println("Erro:", err)
} else {
fmt.Printf("Nome: %s, ID: %d\n", nome, id)
}
}
Nomeando Retornos
Go permite nomear os valores de retorno na assinatura da função. Quando você faz isso, essas variáveis são inicializadas com seus valores zero automaticamente e podem ser retornadas implicitamente com a instrução return vazia. Isso torna o código mais legível, especialmente em funções com muitos retornos, mas use com moderação — retornos vazios podem obscurecer a lógica se abusados.
package main
import "fmt"
// Retornos nomeados - bom para documentação
func calcularMedia(notas []float64) (media float64, total float64, err error) {
if len(notas) == 0 {
err = fmt.Errorf("nenhuma nota fornecida")
return
}
for _, nota := range notas {
total += nota
}
media = total / float64(len(notas))
return
}
// Sem retornos nomeados - mais explícito
func calcularMediaExplicito(notas []float64) (float64, float64, error) {
if len(notas) == 0 {
return 0, 0, fmt.Errorf("nenhuma nota fornecida")
}
var media, total float64
for _, nota := range notas {
total += nota
}
media = total / float64(len(notas))
return media, total, nil
}
func main() {
notas := []float64{7.5, 8.0, 9.5}
media, total, err := calcularMedia(notas)
if err != nil {
fmt.Println("Erro:", err)
} else {
fmt.Printf("Média: %.2f, Total: %.2f\n", media, total)
}
}
Funções Variádicas
Funções variádicas são aquelas que aceitam um número indefinido de argumentos do mesmo tipo. Você as declara usando reticências (...) antes do tipo do parâmetro. Internamente, Go converte esses argumentos em um slice, então você manipula como tal. Essa abordagem é muito mais elegante do que passar um slice e evita a necessidade de wrapping manual.
A vantagem principal é a liberdade do chamador: pode passar zero argumentos, um ou vários, tudo com a mesma sintaxe intuitiva. Go usa isso extensivamente, como em fmt.Println() que aceita quantos valores você quiser imprimir.
package main
import (
"fmt"
"strings"
)
// Função variádica básica
func somar(numeros ...int) int {
total := 0
for _, num := range numeros {
total += num
}
return total
}
// Variádica com múltiplos retornos
func processarStrings(separador string, textos ...string) (resultado string, contagem int) {
resultado = strings.Join(textos, separador)
contagem = len(textos)
return
}
// Variádica com argumentos fixos antes
func criarMensagem(prefixo string, palavras ...string) string {
return prefixo + ": " + strings.Join(palavras, ", ")
}
func main() {
// Chamadas com diferentes quantidades de argumentos
fmt.Println("Soma de 1,2,3:", somar(1, 2, 3))
fmt.Println("Soma de 5,10:", somar(5, 10))
fmt.Println("Soma vazia:", somar())
// Expandindo um slice com ...
numeros := []int{4, 5, 6}
fmt.Println("Soma do slice:", somar(numeros...))
// Variádica com outros parâmetros
resultado, cont := processarStrings(" | ", "Go", "é", "legal")
fmt.Printf("Resultado: %s (contagem: %d)\n", resultado, cont)
msg := criarMensagem("Linguagens", "Go", "Rust", "Python")
fmt.Println(msg)
}
Cuidados com Variádicas
Uma armadilha comum é tentar passar um slice diretamente sem usar o operador de expansão (...). Sem ele, você estará passando o próprio slice como um único argumento, não seus elementos. Além disso, a variádica deve ser sempre o último parâmetro da função — você não pode ter parâmetros após ela.
package main
import "fmt"
func imprimirNomes(nomes ...string) {
for _, nome := range nomes {
fmt.Println(nome)
}
}
// ERRADO: variádica não é o último parâmetro
// func errado(nomes ...string, idade int) {} // Isso não compila
func main() {
// Correto: usando operador de expansão
lista := []string{"Alice", "Bob", "Charlie"}
imprimirNomes(lista...)
// Direto com literais
imprimirNomes("Diana", "Eve")
// Sem argumentos (válido)
imprimirNomes()
}
Funções como Valores
Em Go, funções são cidadãs de primeira classe — você pode atribuir uma função a uma variável, passá-la como argumento, retorná-la de outra função ou armazená-la em uma estrutura. Isso abre possibilidades poderosas para programação funcional e patterns como callbacks, middlewares e estratégias.
O tipo de uma função é definido pela sua assinatura: quantos e quais parâmetros ela aceita, e quantos e quais valores retorna. Duas funções só são do mesmo tipo se tiverem exatamente a mesma assinatura.
package main
import (
"fmt"
"sort"
"strings"
)
// Definindo um tipo de função
type Operacao func(int, int) int
// Função que retorna uma função
func criarMultiplicador(fator int) func(int) int {
return func(x int) int {
return x * fator
}
}
// Função que recebe uma função como argumento
func aplicarOperacao(a, b int, op Operacao) int {
return op(a, b)
}
// Função que filtra usando uma função predicado
func filtrar(numeros []int, predicado func(int) bool) []int {
resultado := []int{}
for _, num := range numeros {
if predicado(num) {
resultado = append(resultado, num)
}
}
return resultado
}
func main() {
// Atribuindo funções a variáveis
somar := func(a, b int) int {
return a + b
}
subtrair := func(a, b int) int {
return a - b
}
fmt.Println("Soma:", aplicarOperacao(10, 5, somar))
fmt.Println("Subtração:", aplicarOperacao(10, 5, subtrair))
// Usando tipo de função definido
var op Operacao
op = func(a, b int) int { return a * b }
fmt.Println("Multiplicação:", aplicarOperacao(10, 5, op))
// Retornando uma função
vezes3 := criarMultiplicador(3)
fmt.Println("10 * 3 =", vezes3(10))
vezes5 := criarMultiplicador(5)
fmt.Println("10 * 5 =", vezes5(10))
// Passando função como argumento
numeros := []int{1, 2, 3, 4, 5, 6, 7, 8}
pares := filtrar(numeros, func(n int) bool { return n%2 == 0 })
fmt.Println("Números pares:", pares)
maiores := filtrar(numeros, func(n int) bool { return n > 4 })
fmt.Println("Maiores que 4:", maiores)
}
Closures e Captura de Variáveis
Funções anônimas em Go podem capturar variáveis do escopo externo, criando closures. A captura é por referência, não por valor — se a variável externa muda, a função enxerga a mudança. Essa característica é essencial para patterns como callbacks e decoradores.
package main
import "fmt"
func criarContador() func() int {
count := 0
return func() int {
count++
return count
}
}
func aplicarFiltros(palavra string, filtros ...func(string) string) string {
resultado := palavra
for _, filtro := range filtros {
resultado = filtro(resultado)
}
return resultado
}
func main() {
// Closure capturando variável
contador := criarContador()
fmt.Println(contador()) // 1
fmt.Println(contador()) // 2
fmt.Println(contador()) // 3
// Múltiplos contadores independentes
contador2 := criarContador()
fmt.Println(contador2()) // 1 (começa do zero novamente)
// Usando closures como filtros
converterMaiuscula := func(s string) string {
return strings.ToUpper(s)
}
adicionarPrefixo := func(s string) string {
return ">>> " + s
}
resultado := aplicarFiltros("hello", converterMaiuscula, adicionarPrefixo)
fmt.Println(resultado) // >>> HELLO
}
Armazenando Funções em Estruturas
Um padrão poderoso é armazenar funções dentro de structs. Isso permite criar objetos com comportamento configurável ou implementar padrões de design como Strategy ou Command.
package main
import "fmt"
type Logger struct {
logFunc func(string)
}
type Calculadora struct {
operacao func(int, int) int
nome string
}
func main() {
// Configurando logger com diferentes implementações
loggerConsole := Logger{
logFunc: func(msg string) {
fmt.Println("[CONSOLE]", msg)
},
}
loggerArquivo := Logger{
logFunc: func(msg string) {
fmt.Println("[ARQUIVO]", msg)
},
}
loggerConsole.logFunc("Aplicação iniciada")
loggerArquivo.logFunc("Log escrito em arquivo")
// Calculadora com estratégia plugável
calc1 := Calculadora{
operacao: func(a, b int) int { return a + b },
nome: "Adição",
}
calc2 := Calculadora{
operacao: func(a, b int) int { return a * b },
nome: "Multiplicação",
}
fmt.Printf("%s: %d\n", calc1.nome, calc1.operacao(5, 3))
fmt.Printf("%s: %d\n", calc2.nome, calc2.operacao(5, 3))
}
Combinando os Três Conceitos
Agora que você compreende cada mecanismo isoladamente, vamos combiná-los em padrões mais realistas que você encontrará em código profissional.
package main
import (
"fmt"
"sort"
)
// Tipo que representa uma transformação
type Transformacao func(string) string
// Função que retorna uma transformação e possível erro
func obterTransformacao(tipo string) (Transformacao, error) {
switch tipo {
case "maiuscula":
return func(s string) string {
return strings.ToUpper(s)
}, nil
case "minuscula":
return func(s string) string {
return strings.ToLower(s)
}, nil
default:
return nil, fmt.Errorf("tipo de transformação desconhecido: %s", tipo)
}
}
// Função que aceita múltiplos transformadores e retorna resultado + contagem
func aplicarTransformacoes(texto string, transformadores ...Transformacao) (string, int) {
resultado := texto
for _, transformador := range transformadores {
resultado = transformador(resultado)
}
return resultado, len(transformadores)
}
// Processador que armazena funções variádicas
type Processador struct {
filtros []func(int) bool
}
func (p *Processador) adicionarFiltro(f func(int) bool) {
p.filtros = append(p.filtros, f)
}
func (p *Processador) processar(numeros ...int) []int {
resultado := []int{}
for _, num := range numeros {
passou := true
for _, filtro := range p.filtros {
if !filtro(num) {
passou = false
break
}
}
if passou {
resultado = append(resultado, num)
}
}
return resultado
}
func main() {
// Combinando retorno múltiplo + funções como valor
transform, err := obterTransformacao("maiuscula")
if err != nil {
fmt.Println("Erro:", err)
return
}
// Usando variádica com funções
resultado, quantidade := aplicarTransformacoes(
"hello world",
transform,
func(s string) string { return ">>> " + s },
)
fmt.Printf("Resultado: %s (%d transformações aplicadas)\n", resultado, quantidade)
// Processador com múltiplos filtros
proc := &Processador{}
proc.adicionarFiltro(func(n int) bool { return n > 5 })
proc.adicionarFiltro(func(n int) bool { return n < 20 })
numeros := []int{1, 6, 10, 15, 25, 30}
filtrados := proc.processar(numeros...)
fmt.Println("Números filtrados:", filtrados)
}