Go

O que Todo Dev Deve Saber sobre Funções em Go: Múltiplos Retornos, Variádicas e Funções como Valores

12 min de leitura

O que Todo Dev Deve Saber sobre Funções em Go: Múltiplos Retornos, Variádicas e Funções como Valores

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: 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 é

<h2>Múltiplos Retornos em Go</h2>

<p>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.</p>

<p>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:</p>

<pre><code class="language-go">package main

import (

&quot;fmt&quot;

&quot;strconv&quot;

)

func dividir(a, b float64) (float64, error) {

if b == 0 {

return 0, fmt.Errorf(&quot;divisão por zero não permitida&quot;)

}

return a / b, nil

}

func buscarUsuario(id int) (string, int, error) {

if id &lt;= 0 {

return &quot;&quot;, 0, fmt.Errorf(&quot;ID inválido&quot;)

}

// Simulando busca em banco de dados

usuarios := map[int]string{1: &quot;Alice&quot;, 2: &quot;Bob&quot;}

nome, existe := usuarios[id]

if !existe {

return &quot;&quot;, 0, fmt.Errorf(&quot;usuário não encontrado&quot;)

}

return nome, id, nil

}

func main() {

// Capturando múltiplos retornos

resultado, err := dividir(10, 2)

if err != nil {

fmt.Println(&quot;Erro:&quot;, err)

} else {

fmt.Printf(&quot;Resultado: %.2f\n&quot;, resultado)

}

// Descartando retornos com blank identifier

nome, _, err := buscarUsuario(1)

if err != nil {

fmt.Println(&quot;Erro:&quot;, err)

} else {

fmt.Println(&quot;Usuário encontrado:&quot;, nome)

}

// Capturando todos os retornos

nome, id, err := buscarUsuario(2)

if err != nil {

fmt.Println(&quot;Erro:&quot;, err)

} else {

fmt.Printf(&quot;Nome: %s, ID: %d\n&quot;, nome, id)

}

}</code></pre>

<h3>Nomeando Retornos</h3>

<p>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 <code>return</code> 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.</p>

<pre><code class="language-go">package main

import &quot;fmt&quot;

// Retornos nomeados - bom para documentação

func calcularMedia(notas []float64) (media float64, total float64, err error) {

if len(notas) == 0 {

err = fmt.Errorf(&quot;nenhuma nota fornecida&quot;)

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(&quot;nenhuma nota fornecida&quot;)

}

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(&quot;Erro:&quot;, err)

} else {

fmt.Printf(&quot;Média: %.2f, Total: %.2f\n&quot;, media, total)

}

}</code></pre>

<h2>Funções Variádicas</h2>

<p>Funções variádicas são aquelas que aceitam um número indefinido de argumentos do mesmo tipo. Você as declara usando reticências (<code>...</code>) 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.</p>

<p>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 <code>fmt.Println()</code> que aceita quantos valores você quiser imprimir.</p>

<pre><code class="language-go">package main

import (

&quot;fmt&quot;

&quot;strings&quot;

)

// 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 + &quot;: &quot; + strings.Join(palavras, &quot;, &quot;)

}

func main() {

// Chamadas com diferentes quantidades de argumentos

fmt.Println(&quot;Soma de 1,2,3:&quot;, somar(1, 2, 3))

fmt.Println(&quot;Soma de 5,10:&quot;, somar(5, 10))

fmt.Println(&quot;Soma vazia:&quot;, somar())

// Expandindo um slice com ...

numeros := []int{4, 5, 6}

fmt.Println(&quot;Soma do slice:&quot;, somar(numeros...))

// Variádica com outros parâmetros

resultado, cont := processarStrings(&quot; | &quot;, &quot;Go&quot;, &quot;é&quot;, &quot;legal&quot;)

fmt.Printf(&quot;Resultado: %s (contagem: %d)\n&quot;, resultado, cont)

msg := criarMensagem(&quot;Linguagens&quot;, &quot;Go&quot;, &quot;Rust&quot;, &quot;Python&quot;)

fmt.Println(msg)

}</code></pre>

<h3>Cuidados com Variádicas</h3>

<p>Uma armadilha comum é tentar passar um slice diretamente sem usar o operador de expansão (<code>...</code>). 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.</p>

<pre><code class="language-go">package main

import &quot;fmt&quot;

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{&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;}

imprimirNomes(lista...)

// Direto com literais

imprimirNomes(&quot;Diana&quot;, &quot;Eve&quot;)

// Sem argumentos (válido)

imprimirNomes()

}</code></pre>

<h2>Funções como Valores</h2>

<p>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.</p>

<p>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.</p>

<pre><code class="language-go">package main

import (

&quot;fmt&quot;

&quot;sort&quot;

&quot;strings&quot;

)

// 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(&quot;Soma:&quot;, aplicarOperacao(10, 5, somar))

fmt.Println(&quot;Subtração:&quot;, aplicarOperacao(10, 5, subtrair))

// Usando tipo de função definido

var op Operacao

op = func(a, b int) int { return a * b }

fmt.Println(&quot;Multiplicação:&quot;, aplicarOperacao(10, 5, op))

// Retornando uma função

vezes3 := criarMultiplicador(3)

fmt.Println(&quot;10 * 3 =&quot;, vezes3(10))

vezes5 := criarMultiplicador(5)

fmt.Println(&quot;10 * 5 =&quot;, 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(&quot;Números pares:&quot;, pares)

maiores := filtrar(numeros, func(n int) bool { return n &gt; 4 })

fmt.Println(&quot;Maiores que 4:&quot;, maiores)

}</code></pre>

<h3>Closures e Captura de Variáveis</h3>

<p>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.</p>

<pre><code class="language-go">package main

import &quot;fmt&quot;

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 &quot;&gt;&gt;&gt; &quot; + s

}

resultado := aplicarFiltros(&quot;hello&quot;, converterMaiuscula, adicionarPrefixo)

fmt.Println(resultado) // &gt;&gt;&gt; HELLO

}</code></pre>

<h3>Armazenando Funções em Estruturas</h3>

<p>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.</p>

<pre><code class="language-go">package main

import &quot;fmt&quot;

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(&quot;[CONSOLE]&quot;, msg)

},

}

loggerArquivo := Logger{

logFunc: func(msg string) {

fmt.Println(&quot;[ARQUIVO]&quot;, msg)

},

}

loggerConsole.logFunc(&quot;Aplicação iniciada&quot;)

loggerArquivo.logFunc(&quot;Log escrito em arquivo&quot;)

// Calculadora com estratégia plugável

calc1 := Calculadora{

operacao: func(a, b int) int { return a + b },

nome: &quot;Adição&quot;,

}

calc2 := Calculadora{

operacao: func(a, b int) int { return a * b },

nome: &quot;Multiplicação&quot;,

}

fmt.Printf(&quot;%s: %d\n&quot;, calc1.nome, calc1.operacao(5, 3))

fmt.Printf(&quot;%s: %d\n&quot;, calc2.nome, calc2.operacao(5, 3))

}</code></pre>

<h2>Combinando os Três Conceitos</h2>

<p>Agora que você compreende cada mecanismo isoladamente, vamos combiná-los em padrões mais realistas que você encontrará em código profissional.</p>

<pre><code class="language-go">package main

import (

&quot;fmt&quot;

&quot;sort&quot;

)

// 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 &quot;maiuscula&quot;:

return func(s string) string {

return strings.ToUpper(s)

}, nil

case &quot;minuscula&quot;:

return func(s string) string {

return strings.ToLower(s)

}, nil

default:

return nil, fmt.Errorf(&quot;tipo de transformação desconhecido: %s&quot;, 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(&quot;maiuscula&quot;)

if err != nil {

fmt.Println(&quot;Erro:&quot;, err)

return

}

// Usando variádica com funções

resultado, quantidade := aplicarTransformacoes(

&quot;hello world&quot;,

transform,

func(s string) string { return &quot;&gt;&gt;&gt; &quot; + s },

)

fmt.Printf(&quot;Resultado: %s (%d transformações aplicadas)\n&quot;, resultado, quantidade)

// Processador com múltiplos filtros

proc := &amp;Processador{}

proc.adicionarFiltro(func(n int) bool { return n &gt; 5 })

proc.adicionarFiltro(func(n int) bool { return n &lt; 20 })

numeros := []int{1, 6, 10, 15, 25, 30}

filtrados := proc.processar(numeros...)

fmt.Println(&quot;Números filtrados:&quot;, filtrados)

}</code></pre>

<h2>Referências</h2>

<ul>

<li><a href="https://golang.org/doc/effective_go#functions" target="_blank" rel="noopener noreferrer">Documentação Oficial - Functions</a></li>

<li><a href="https://go.dev/blog/error-handling-and-go" target="_blank" rel="noopener noreferrer">The Go Blog - Error Handling and Go</a></li>

<li><a href="https://golang.org/doc/effective_go#multiple-returns" target="_blank" rel="noopener noreferrer">Effective Go - Multiple Return Values</a></li>

<li><a href="https://gobyexample.com/variadic-functions" target="_blank" rel="noopener noreferrer">Go by Example - Variadic Functions</a></li>

<li><a href="https://gobyexample.com/first-class-functions" target="_blank" rel="noopener noreferrer">Go by Example - First Class Functions</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

Pacote encoding/json em Go: Serialização, Tags e Casos Especiais: Do Básico ao Avançado
Pacote encoding/json em Go: Serialização, Tags e Casos Especiais: Do Básico ao Avançado

Introdução ao Pacote encoding/json em Go O pacote é uma das ferramentas mais...

Guia Completo de Observabilidade em Go: OpenTelemetry, Métricas e Tracing Distribuído
Guia Completo de Observabilidade em Go: OpenTelemetry, Métricas e Tracing Distribuído

O que é Observabilidade e por que você precisa dela Observabilidade é a capac...

O que Todo Dev Deve Saber sobre Garbage Collector em Go: Funcionamento Interno e Impacto na Performance
O que Todo Dev Deve Saber sobre Garbage Collector em Go: Funcionamento Interno e Impacto na Performance

Introdução: O que é o Garbage Collector em Go? O Garbage Collector (GC) é um...