Golang

Funções: declaração, múltiplos retornos e variádicas Já leu

8 min de leitura

Funções: declaração, múltiplos retornos e variádicas
Em Go, funções são cidadãs de primeira classe. Elas podem ser atribuídas a variáveis, passadas como argumentos, retorn

Em Go, funções são cidadãs de primeira classe. Elas podem ser atribuídas a variáveis, passadas como argumentos, retornadas por outras funções e declaradas anonimamente. Ao mesmo tempo, a linguagem impõe uma sintaxe clara e consistente que torna qualquer função Go imediatamente legível, independentemente de quem a escreveu.

Entender funções em profundidade é essencial porque praticamente tudo em Go passa por elas — desde o tratamento de erros até os padrões de concorrência que serão estudados nos módulos seguintes.


Declaração básica

A sintaxe de uma função em Go segue sempre a mesma estrutura: palavra-chave func, nome, parâmetros entre parênteses, tipo de retorno e corpo entre chaves:

package main

import "fmt"

func saudar(nome string) string {
    return "Olá, " + nome + "!"
}

func main() {
    mensagem := saudar("Ricardo")
    fmt.Println(mensagem) // Olá, Ricardo!
}

Quando a função não retorna nada, o tipo de retorno é simplesmente omitido:

func imprimirLinha(texto string) {
    fmt.Println("→", texto)
}

Parâmetros do mesmo tipo

Quando múltiplos parâmetros consecutivos têm o mesmo tipo, Go permite declarar o tipo apenas uma vez ao final:

// Forma verbosa
func somar(a int, b int) int {
    return a + b
}

// Forma idiomática
func somar(a, b int) int {
    return a + b
}

Essa sintaxe se estende a quantos parâmetros forem necessários:

func calcularMedia(a, b, c float64) float64 {
    return (a + b + c) / 3
}

Múltiplos valores de retorno

Esta é uma das características mais distintivas do Go. Funções podem retornar múltiplos valores, e isso não é um recurso de nicho — é o mecanismo central pelo qual Go trata erros.

package main

import (
    "errors"
    "fmt"
)

func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("divisão por zero não é permitida")
    }
    return a / b, nil
}

func main() {
    resultado, err := dividir(10, 3)
    if err != nil {
        fmt.Println("Erro:", err)
        return
    }
    fmt.Printf("Resultado: %.4f\n", resultado) // 3.3333
}

O padrão (resultado, error) está presente em praticamente toda função Go que pode falhar. O chamador é obrigado a lidar com ambos os valores — ou descartar explicitamente um deles com _. Esse design torna o tratamento de erros visível e auditável.


Retornos nomeados

Go permite dar nomes aos valores de retorno. Esses nomes funcionam como variáveis locais pré-declaradas e podem ser usados com o return nu — um return sem argumentos que devolve os valores nomeados no estado atual:

func minMax(nums []int) (min, max int) {
    min = nums[0]
    max = nums[0]

    for _, n := range nums[1:] {
        if n < min {
            min = n
        }
        if n > max {
            max = n
        }
    }

    return // retorna min e max implicitamente
}

func main() {
    menor, maior := minMax([]int{3, 1, 7, 2, 9, 4})
    fmt.Println(menor, maior) // 1 9
}

Retornos nomeados são úteis quando a função é curta e os nomes adicionam clareza à assinatura. Em funções longas, o return nu pode obscurecer o que está sendo retornado — nesses casos, prefira o retorno explícito.


Funções variádicas

Uma função variádica aceita um número indefinido de argumentos do mesmo tipo. O último parâmetro recebe o prefixo ... e é tratado internamente como um slice:

func somarTodos(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    fmt.Println(somarTodos(1, 2, 3))          // 6
    fmt.Println(somarTodos(10, 20, 30, 40))   // 100
    fmt.Println(somarTodos())                  // 0
}

Quando você já tem um slice e quer passá-lo para uma função variádica, use o operador ... na chamada para expandir o slice:

numeros := []int{5, 10, 15, 20}
fmt.Println(somarTodos(numeros...)) // 50

A função fmt.Println é ela própria variádica — aceita qualquer número de argumentos de qualquer tipo, graças ao uso de ...interface{} (ou ...any na notação moderna).


Funções como valores

Em Go, funções têm tipo assim como int ou string têm. Uma variável pode armazenar uma função, e funções podem ser passadas como argumentos ou retornadas por outras funções:

package main

import "fmt"

func aplicar(nums []int, fn func(int) int) []int {
    resultado := make([]int, len(nums))
    for i, n := range nums {
        resultado[i] = fn(n)
    }
    return resultado
}

func dobrar(n int) int { return n * 2 }
func quadrado(n int) int { return n * n }

func main() {
    nums := []int{1, 2, 3, 4, 5}

    fmt.Println(aplicar(nums, dobrar))    // [2 4 6 8 10]
    fmt.Println(aplicar(nums, quadrado))  // [1 4 9 16 25]
}

O tipo func(int) int descreve uma função que recebe um int e retorna um int. Esse tipo pode ser nomeado com type para tornar o código mais legível:

type Transformador func(int) int

func aplicar(nums []int, fn Transformador) []int {
    // ...
}

Funções anônimas e closures

Funções anônimas são funções sem nome, declaradas diretamente onde são usadas. Quando uma função anônima referencia variáveis do escopo externo, ela forma uma closure — capturando essas variáveis e mantendo acesso a elas mesmo após o escopo original ter encerrado:

package main

import "fmt"

func contadorFabrica() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    contador := contadorFabrica()

    fmt.Println(contador()) // 1
    fmt.Println(contador()) // 2
    fmt.Println(contador()) // 3

    outroContador := contadorFabrica()
    fmt.Println(outroContador()) // 1 — estado independente
}

A variável count é capturada pelo closure. Cada chamada a contadorFabrica cria um novo count independente. Esse padrão é muito utilizado para criar geradores, middlewares e funções com estado encapsulado.


Funções anônimas executadas imediatamente

Uma função anônima pode ser declarada e chamada na mesma expressão. Esse padrão é chamado de IIFE — Immediately Invoked Function Expression:

resultado := func(a, b int) int {
    return a + b
}(10, 20)

fmt.Println(resultado) // 30

Em Go, IIFEs são usadas com frequência junto ao defer e a goroutines para isolar escopo ou capturar variáveis em seu valor atual dentro de laços.


Armadilha clássica: closure em laço

Um erro comum ao usar closures dentro de laços é capturar a variável do laço por referência em vez do valor atual:

// Código com problema
funcs := make([]func(), 3)
for i := 0; i < 3; i++ {
    funcs[i] = func() {
        fmt.Println(i) // captura a variável i, não o valor
    }
}

for _, f := range funcs {
    f()
}
// Imprime: 3 3 3 — não 0 1 2

A correção é criar uma cópia local da variável dentro do laço:

for i := 0; i < 3; i++ {
    i := i  // nova variável i local a cada iteração
    funcs[i] = func() {
        fmt.Println(i)
    }
}
// Imprime: 0 1 2

Funções init

Go permite declarar funções especiais chamadas init em qualquer arquivo de um pacote. Elas são executadas automaticamente antes da função main, sem precisar ser chamadas explicitamente. São usadas para inicializar estado de pacote, registrar drivers ou validar configurações:

package main

import "fmt"

var configuracao string

func init() {
    configuracao = "produção"
    fmt.Println("init executado")
}

func main() {
    fmt.Println("main executado")
    fmt.Println("Ambiente:", configuracao)
}
// Saída:
// init executado
// main executado
// Ambiente: produção

Um arquivo pode ter múltiplas funções init, e pacotes importados têm seus init executados antes do pacote que os importa. O uso deve ser moderado — lógica complexa em init dificulta testes e rastreamento de bugs.


Resumo do que foi coberto

Este artigo apresentou funções em Go de forma abrangente: declaração básica, agrupamento de parâmetros, múltiplos retornos com o padrão de erro, retornos nomeados, funções variádicas, funções como valores, closures e sua armadilha em laços, IIFEs e funções init. Esses recursos formam a base de toda a expressividade do Go.


Referências e leituras complementares

 

Comentários

Mais em Golang

Go Modules: go.mod, go.sum e gerenciamento de dependências
Go Modules: go.mod, go.sum e gerenciamento de dependências

Antes dos m&oacute;dulos, Go usava o GOPATH &mdash; um diret&oacute;rio globa...

Instalação, configuração do ambiente e o comando `go`
Instalação, configuração do ambiente e o comando `go`

&nbsp; Antes de escrever c&oacute;digo, o ambiente que precisaremos deve est...

Interfaces: contratos implícitos e polimorfismo
Interfaces: contratos implícitos e polimorfismo

&nbsp; Interfaces existem em Java, C#, TypeScript e diversas outras linguage...