Funções são blocos fundamentais de código em Go, permitindo encapsular lógica, reutilizar código e organizar programas de forma modular. Nesta aula, exploraremos a sintaxe de declaração, os recursos avançados como múltiplos retornos e parâmetros variádicos, além das funções com retornos nomeados, que tornam o código mais expressivo e seguro.

Go adota uma abordagem simples e direta para funções, mas oferece construções poderosas que facilitam a escrita de código limpo e eficiente. Vamos a cada tópico com exemplos práticos.

Declaração

Em Go, a declaração de uma função usa a palavra-chave func, seguida do nome, parâmetros entre parênteses e o tipo de retorno (se houver). O corpo da função é delimitado por chaves. Diferente de algumas linguagens, o tipo do retorno é escrito após os parâmetros.

A sintaxe básica é:

func nome(param1 tipo1, param2 tipo2) tipoRetorno {
    // corpo
    return valor
}

Exemplo de função que soma dois inteiros:

package main

import "fmt"

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

func main() {
    resultado := somar(3, 4)
    fmt.Println("Soma:", resultado) // Soma: 7
}

Se os parâmetros consecutivos têm o mesmo tipo, podemos omitir o tipo dos anteriores:

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

Funções também podem não ter retorno (apenas executar ações):

func saudacao(nome string) {
    fmt.Println("Olá,", nome)
}

E podem retornar múltiplos valores, como veremos a seguir.

Múltiplos retornos

Go suporta que uma função retorne mais de um valor. Isso é útil para retornar um resultado e um erro, ou vários resultados de uma operação. A sintaxe lista os tipos de retorno separados por vírgula, entre parênteses.

func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divisão por zero")
    }
    return a / b, nil
}

No chamador, podemos capturar os retornos em variáveis separadas:

resultado, err := dividir(10, 2)
if err != nil {
    fmt.Println("Erro:", err)
} else {
    fmt.Println("Resultado:", resultado)
}

Se não precisarmos de um dos retornos, usamos o identificador em branco _:

resultado, _ := dividir(10, 0) // ignoramos o erro (não recomendado em geral)
fmt.Println(resultado)

Múltiplos retornos são amplamente usados em Go, especialmente para propagar erros de forma explícita.

Parâmetros variádicos

Parâmetros variádicos permitem que uma função receba um número variável de argumentos do mesmo tipo. Eles são declarados com reticências (...) antes do tipo. Internamente, o parâmetro se comporta como um slice.

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

Exemplo de uso:

fmt.Println(somarValores(1, 2, 3))       // 6
fmt.Println(somarValores(10, 20))        // 30
fmt.Println(somarValores())              // 0

Se já tivermos um slice, podemos passá-lo como argumento variádico usando a sintaxe slice...:

nums := []int{1, 2, 3, 4}
fmt.Println(somarValores(nums...)) // 10

Uma função pode ter no máximo um parâmetro variádico, e ele deve ser o último parâmetro.

Funções nomeadas de retorno

Em Go, podemos dar nomes aos valores de retorno de uma função. Isso serve como documentação e permite usar return sem argumentos (return naked). Os nomes se comportam como variáveis locais, inicializadas com zero.

func dividir(a, b float64) (resultado float64, erro error) {
    if b == 0 {
        erro = fmt.Errorf("divisão por zero")
        return
    }
    resultado = a / b
    return
}

No exemplo acima, os retornos nomeados resultado e erro são declarados na assinatura. Dentro da função, podemos atribuir valores a eles e usar return vazio para retornar seus valores atuais. Isso pode tornar o código mais limpo, especialmente em funções com muitos pontos de retorno.

No entanto, é preciso cuidado: o uso de retornos nus (naked returns) pode prejudicar a legibilidade em funções longas. A recomendação é usá-los apenas em funções curtas.

Exemplo completo:

package main

import "fmt"

func main() {
    res, err := dividir(10, 2)
    if err != nil {
        fmt.Println("Erro:", err)
    } else {
        fmt.Println("Resultado:", res)
    }
}

Boas práticas

Ao declarar funções em Go, prefira nomes descritivos e mantenha funções curtas (idealmente até 20-30 linhas). Use múltiplos retornos para erros em vez de exceções. Evite retornos nomeados em funções longas, pois podem dificultar o entendimento. Para parâmetros variádicos, lembre-se de que eles criam uma cópia do slice? Na verdade, o slice é passado por referência, mas o ponteiro para o array subjacente é copiado; modificações nos elementos afetam o original. Cuidado com efeitos colaterais.

Referências

Exercícios

  1. Escreva uma função chamada maximo que receba dois inteiros e retorne o maior deles. Teste com os valores 10 e 7.

    ✓ Resposta:
    func maximo(a, b int) int {
        if a > b {
            return a
        }
        return b
    }
    
    // Teste
    func main() {
        fmt.Println(maximo(10, 7)) // 10
    }
  2. Crie uma função trocar que receba dois inteiros e retorne os valores trocados (use múltiplos retornos). Teste com (1, 2).

    ✓ Resposta:
    func trocar(a, b int) (int, int) {
        return b, a
    }
    
    // Teste
    func main() {
        x, y := trocar(1, 2)
        fmt.Println(x, y) // 2 1
    }
  3. Implemente uma função media que aceite um número variável de floats e retorne a média. Teste com (1.5, 2.5, 3.0).

    ✓ Resposta:
    func media(nums ...float64) float64 {
        total := 0.0
        for _, n := range nums {
            total += n
        }
        return total / float64(len(nums))
    }
    
    // Teste
    func main() {
        fmt.Println(media(1.5, 2.5, 3.0)) // 2.333...
    }
  4. Escreva uma função dividirSeguro com retornos nomeados (quociente e erro) que divide dois números. Teste com divisão por zero.

    ✓ Resposta:
    func dividirSeguro(a, b float64) (quociente float64, erro error) {
        if b == 0 {
            erro = fmt.Errorf("divisão por zero")
            return
        }
        quociente = a / b
        return
    }
    
    // Teste
    func main() {
        q, err := dividirSeguro(10, 0)
        if err != nil {
            fmt.Println("Erro:", err)
        } else {
            fmt.Println("Quociente:", q)
        }
    }
  5. Crie uma função concatenar que receba um separador string e um número variádico de strings, e retorne a concatenação de todas as strings separadas pelo separador. Teste com (", ", "a", "b", "c").

    ✓ Resposta:
    func concatenar(sep string, strs ...string) string {
        resultado := ""
        for i, s := range strs {
            if i > 0 {
                resultado += sep
            }
            resultado += s
        }
        return resultado
    }
    
    // Teste
    func main() {
        fmt.Println(concatenar(", ", "a", "b", "c")) // "a, b, c"
    }