Funções são blocos de código reutilizáveis que executam uma tarefa específica. Em Go, as funções são cidadãs de primeira classe, podendo ser atribuídas a variáveis, passadas como argumentos e retornadas de outras funções. Nesta aula, você aprenderá a declarar funções, trabalhar com múltiplos retornos, usar parâmetros variádicos e explorar as funções com retorno nomeado, um recurso que torna o código mais expressivo.

Go adota uma sintaxe concisa e clara para funções, com tipos declarados após os nomes dos parâmetros. A linguagem também oferece suporte nativo a múltiplos valores de retorno, o que é útil para retornar resultados e erros simultaneamente. Vamos mergulhar em cada tópico com exemplos práticos.

Declaração

Em Go, a declaração de uma função começa com a palavra-chave func, seguida pelo nome, parâmetros entre parênteses e o tipo de retorno (se houver). O corpo da função é delimitado por chaves. A sintaxe básica é:

func nome(parametros) tipo_retorno {
    // corpo
}

Parâmetros são especificados com nome e tipo, separados por vírgula. Se dois ou mais parâmetros consecutivos têm o mesmo tipo, o tipo pode ser declarado apenas no último. Funções que não retornam valor não precisam de tipo de retorno. Exemplo:

package main

import "fmt"

// Função que não retorna valor
func saudacao(nome string) {
    fmt.Printf("Olá, %s!\n", nome)
}

// Função que retorna um inteiro
func soma(a, b int) int {
    return a + b
}

func main() {
    saudacao("Maria")
    resultado := soma(3, 4)
    fmt.Println("Soma:", resultado)
}

Observe que a função soma declara a, b int sem repetir o tipo. A chamada saudacao("Maria") executa a função, e soma(3, 4) retorna um valor que é armazenado na variável resultado.

Múltiplos retornos

Go permite que uma função retorne múltiplos valores. Isso é comum para retornar um resultado e um erro, ou vários resultados de uma operação. Os tipos de retorno são listados entre parênteses, separados por vírgula. Exemplo:

package main

import "fmt"

// Retorna dois inteiros
func divisao(dividendo, divisor int) (int, int) {
    quociente := dividendo / divisor
    resto := dividendo % divisor
    return quociente, resto
}

func main() {
    q, r := divisao(10, 3)
    fmt.Printf("Quociente: %d, Resto: %d\n", q, r)
}

Na chamada, os valores retornados são atribuídos a múltiplas variáveis na ordem correspondente. Se você não precisar de um dos retornos, use o identificador em branco _:

q, _ := divisao(10, 3) // ignora o resto
fmt.Println("Quociente:", q)

Múltiplos retornos são frequentemente usados para propagar erros. Por convenção, o erro é o último valor retornado:

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

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

Parâmetros variádicos

Parâmetros variádicos permitem que uma função receba um número variável de argumentos. Em Go, isso é feito usando ... antes do tipo do último parâmetro. Dentro da função, o parâmetro variádico se comporta como um slice. Exemplo:

package main

import "fmt"

func media(numeros ...float64) float64 {
    total := 0.0
    for _, n := range numeros {
        total += n
    }
    return total / float64(len(numeros))
}

func main() {
    fmt.Println("Média:", media(10, 20, 30))          // 20
    fmt.Println("Média:", media(5.5, 6.5, 7.5, 8.5)) // 7.0
}

Você também pode passar um slice para uma função variádica usando a sintaxe slice...:

nums := []float64{1, 2, 3, 4}
fmt.Println("Média:", media(nums...))

Importante: o parâmetro variádico deve ser o último parâmetro. Não é permitido ter mais de um parâmetro variádico.

Funções nomeadas de retorno

Go permite nomear os valores de retorno de uma função. Isso pode tornar o código mais legível, especialmente quando há múltiplos retornos. Os nomes atuam como variáveis declaradas no início da função, e um return sem argumentos retorna os valores atuais dessas variáveis (chamado de bare return). Exemplo:

package main

import "fmt"

func calcular(a, b int) (soma int, produto int) {
    soma = a + b
    produto = a * b
    return // retorna soma e produto automaticamente
}

func main() {
    s, p := calcular(4, 5)
    fmt.Println("Soma:", s, "Produto:", p)
}

Os nomes soma e produto são inicializados com zero (valores zero de int). Dentro da função, você atribui valores a eles e, ao final, um return sem argumentos os retorna. Embora seja conveniente, o uso de bare return pode prejudicar a legibilidade em funções longas; é recomendado apenas em funções curtas.

Você também pode misturar retornos nomeados com retornos explícitos, mas evite confusão. Prefira sempre retornar explicitamente os valores, a menos que a função seja muito simples.

Boas práticas

  • Prefira funções curtas e focadas em uma única tarefa.
  • Use nomes descritivos para parâmetros e retornos.
  • Ao retornar erros, sempre retorne o erro como o último valor.
  • Evite bare return em funções com mais de 10 linhas.
  • Documente funções exportadas com comentários iniciando pelo nome da função.

Referências

Exercícios

  1. Crie uma função chamada maior que recebe dois inteiros e retorna o maior deles. Teste com os valores 10 e 20.

    ✓ Resposta:
    func maior(a, b int) int {
        if a > b {
            return a
        }
        return b
    }
    
    func main() {
        fmt.Println(maior(10, 20)) // 20
    }
  2. Escreva uma função chamada trocar que recebe dois inteiros e retorna os mesmos valores em ordem inversa (primeiro retorna o segundo, e vice-versa). Teste com (1, 2).

    ✓ Resposta:
    func trocar(a, b int) (int, int) {
        return b, a
    }
    
    func main() {
        x, y := trocar(1, 2)
        fmt.Println(x, y) // 2 1
    }
  3. Crie uma função variádica chamada concatenar que recebe strings e as concatena com um espaço entre elas. Teste com "Go", "é", "legal".

    ✓ Resposta:
    func concatenar(palavras ...string) string {
        return strings.Join(palavras, " ")
    }
    
    func main() {
        fmt.Println(concatenar("Go", "é", "legal")) // "Go é legal"
    }
  4. Escreva uma função com retornos nomeados chamada dividir que recebe dois inteiros e retorna quociente e resto (nomes: quo e rem). Use bare return. Teste com 10 e 3.

    ✓ Resposta:
    func dividir(a, b int) (quo, rem int) {
        quo = a / b
        rem = a % b
        return
    }
    
    func main() {
        q, r := dividir(10, 3)
        fmt.Println(q, r) // 3 1
    }
  5. Crie uma função que recebe um slice de inteiros e retorna a soma e a média (como float64). Use múltiplos retornos. Teste com []int{1,2,3,4,5}.

    ✓ Resposta:
    func somaMedia(numeros []int) (int, float64) {
        soma := 0
        for _, n := range numeros {
            soma += n
        }
        media := float64(soma) / float64(len(numeros))
        return soma, media
    }
    
    func main() {
        s, m := somaMedia([]int{1, 2, 3, 4, 5})
        fmt.Println("Soma:", s, "Média:", m) // Soma: 15 Média: 3
    }