Go: Funções em Go

As funções são os “blocos de Lego” do código Go — o Go leva o conceito de “funções de primeira classe” ao extremo, simplificando a programação combinatória.

A filosofia de design de funções do Go: Maximizar a reutilização com sintaxe mínima. Vários valores de retorno, valores de retorno nomeados, argumentos variáveis, closures e passagem de funções por valor — recursos que exigem uma “estrutura” ou “biblioteca” em outras linguagens fazem parte da sintaxe nativa do Go.

1. Você aprenderá


2. A história real de um engenheiro de dados

(1) Problema: as funções em Java só podem retornar um único valor

Bob é um engenheiro de dados que recentemente precisou escrever uma função de processamento de dados:

“Preciso escrever uma função parseUserData que retorne tanto o objeto de usuário analisado quanto um código de erro; ela também precisa registrar o tempo de análise e retornar mensagens de aviso — mas, como o Java permite apenas um único valor de retorno, sou obrigado a enfiar tudo em um único objeto Result, fazendo com que o código pareça um emaranhado de espaguete.”

Ele abriu o código Java:

JAVA
// Java: Can only return one value, so it must be packaged
public class ParseResult {
    public User user;
    public int errorCode;
    public long durationMs;
    public List<String> warnings;
}

public ParseResult parseUserData(String raw) {
    // All 5 return values are listed here
    return new ParseResult(...);
}

Durante uma revisão, um colega reclamou: “Sua função é como um conjunto de bonecas russas — se eu precisar de cinco campos, tenho que ir descascando camada por camada”.

(2) Solução em Go

As funções do Go oferecem suporte nativo a vários valores de retorno:

GO
// user_parser.go
package main

import (
    "fmt"
    "strconv"
    "strings"
    "time"
)

// Multiple return values: user + errorCode + durationMs + warnings
func parseUserData(raw string) (User, int, time.Duration, []string) {
    start := time.Now()
    var warnings []string

    parts := strings.Split(raw, ",")
    if len(parts) != 3 {
        return User{}, 400, time.Since(start), []string{"Format error: 3 fields required"}
    }

    age, err := strconv.Atoi(parts[1])
    if err != nil {
        return User{}, 400, time.Since(start), []string{"Invalid age format"}
    }

    if age < 0 || age > 150 {
        warnings = append(warnings, "Age Anomaly")
    }

    user := User{Name: parts[0], Age: age, City: parts[2]}
    return user, 200, time.Since(start), warnings
}

type User struct {
    Name string
    Age  int
    City string
}

func main() {
    user, code, duration, warnings := parseUserData("Alice,28,Shanghai")

    fmt.Printf("Status: %d\n", code)
    fmt.Printf("User: %+v\n", user)
    fmt.Printf("Duration: %v\n", duration)
    fmt.Printf("Warnings: %v\n", warnings)
}

Resultado:

TEXT 📖 Somente leitura
Status: 200
User: {Name:Alice Age:28 City:Shanghai}
Duration: 12.5µs
Warnings: []

(3) Desempenho: Funções em Go x outras linguagens

Recurso C Java Python Go
Vários valores de retorno ❌ Requer uma estrutura de envoltório ❌ Requer uma classe de envoltório ✅ Tupla ✅ Nativo
Nome do valor de retorno ✅ Nativo
Funções como valores Ponteiros de função Lambda Elementos de primeira classe ✅ Elementos de primeira classe
Fechamentos ✅ (Complexo) Lambda ✅ Conciso
Argumentos de comprimento variável ✅ (stdarg) ✅ varargs ✅ *args ✅ ...
💡 Dica: O suporte do Go a múltiplos valores de retorno tornou “return result + error” uma convenção comum (func foo() (T, error)). Essa é a pedra angular da filosofia de tratamento de erros do Go.

100%
sequenceDiagram
    participant Caller
    participant Function as parseUserData()
    participant Parser as Internal Logic
    Caller->>Function: parseUserData(raw)
    Function->>Parser: Split fields
    Parser-->>Function: name, age, city
    Function-->>Caller: user + 200 + duration + warnings
    Note over Caller: Multiple return values: receive<br/>user/status/time/warnings simultaneously

3. Definição da função func

(1) Os quatro elementos da definição de uma função

GO
func functionName(param1 type1, param2 type2) returnType {
    // Function Body
    return value
}
Elemento Palavra-chave Obrigatório?
Nome da função funcName Sim
Lista de parâmetros (param type, ...) Sim
Tipo de retorno returnType Não (pode ser omitido se não houver valor de retorno)
Corpo da função { ... } Sim

(2) ▶ Exemplo: As funções básicas são definidas de quatro maneiras

GO
package main

import "fmt"

// Form 1: No parameters, no return value
func sayHello() {
    fmt.Println("Hello!")
}

// Form 2: With parameters but no return value
func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

// Form 3: With parameters and a return value
func add(a, b int) int {
    return a + b
}

// Form 4: Multiple Parameters and Multiple Return Values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    sayHello()                              // Hello!
    greet("Alice")                          // Hello, Alice!
    fmt.Println(add(2, 3))                  // 5
    result, err := divide(10.0, 2.0)
    fmt.Printf("%.2f, err=%v\n", result, err)  // 5.00, err=<nil>
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Hello!
Hello, Alice!
5
5.00, err=<nil>

(3) Abreviações de parâmetros

Parâmetros contínuos do mesmo tipo podem ser combinados em um único tipo:

GO
func add(a, b int) int  // equivalent to a int, b int
func rect(w, h int) (int, int)  // both parameters are int

4. Vários valores de retorno

(1) Definição de vários valores de retorno

GO
package main

import "fmt"

func swap(a, b string) (string, string) {
    return b, a
}

func main() {
    x, y := swap("hello", "world")
    fmt.Println(x, y)  // world hello
}

(2) ▶ Exemplo: Valor + Erro (padrão de assinatura do Go)

GO
package main

import (
    "errors"
    "fmt"
)

func findUser(id int) (string, error) {
    if id <= 0 {
        return "", errors.New("invalid id")
    }
    if id == 999 {
        return "", fmt.Errorf("user %d not found", id)
    }
    return fmt.Sprintf("User-%d", id), nil
}

func main() {
    user, err := findUser(1)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Found: %s\n", user)

    // To ignore a return value: use _
    _, err = findUser(999)
    fmt.Printf("Ignored: err=%v\n", err)
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Found: User-1
Ignored: err=user 999 not found

(3) Como nomear o valor de retorno

Os valores de retorno nomeados são declarados no início da função; a instrução return retorna automaticamente essas variáveis:

GO
package main

import "fmt"

func calc(a, b int) (sum, diff, product int) {
    sum = a + b
    diff = a - b
    product = a * b
    return  // bare return, automatically returns sum/diff/product
}

func main() {
    s, d, p := calc(10, 3)
    fmt.Printf("sum=%d, diff=%d, product=%d\n", s, d, p)
}

Saída:

TEXT 📖 Somente leitura
sum=13, diff=7, product=30

(4) Valores de retorno múltiplos versus valores de retorno nomeados

Cenário Recomendação
Retorna 1 valor Valor de retorno padrão
Retorna 2 valores (valor + erro) Valor de retorno padrão
Retorna 3 ou mais valores Nomeie os valores de retorno (para maior clareza)
O valor de retorno precisa ser modificado em defer O valor de retorno deve ser nomeado
💡 Dica: Nomear o valor de retorno torna a assinatura da função mais longa, mas é essencial para modificar o valor de retorno com defer (conforme mostrado no exemplo completo desta lição).


5. Parâmetros variádicos

(1) Sintaxe de argumentos variáveis ...

GO
package main

import "fmt"

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

func main() {
    fmt.Println(sum(1, 2, 3))        // 6
    fmt.Println(sum(10, 20))         // 30
    fmt.Println(sum())               // 0

    // Break the slice apart and pass it in: nums...
    nums := []int{1, 2, 3, 4, 5}
    fmt.Println(sum(nums...))        // 15
}

Saída:

TEXT 📖 Somente leitura
6
30
0
15

(2) ▶ Exemplo: Argumentos variáveis + cadeias formatadas

GO
package main

import "fmt"

// Similar to `fmt.Printf`: The first argument is fixed, and the rest are variable.
func logMessage(level string, args ...interface{}) {
    fmt.Printf("[%s] ", level)
    fmt.Println(args...)  // spread slice and pass to Println
}

func main() {
    logMessage("INFO", "Server started", "on port", 8080)
    logMessage("ERROR", "Database connection failed:", "timeout 5s")
    logMessage("DEBUG")
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
[INFO] Server started on port 8080
[ERROR] Database connection failed: timeout 5s
[DEBUG]

(3) Restrições a argumentos variáveis

Restrição Descrição
Até 1 argumento variável func foo(a int, b ...int)
Os parâmetros variádicos devem ser os últimos func foo(a ...int, b int)
Os tipos devem ser consistentes Para permitir vários tipos, use ...interface{}

6. Funções anônimas e closures

(1) Funções anônimas (literais de função)

As funções anônimas não têm nome e podem ser atribuídas a uma variável ou chamadas diretamente:

GO
package main

import "fmt"

func main() {
    // Assign to a variable
    add := func(a, b int) int {
        return a + b
    }
    fmt.Println(add(2, 3))  // 5

    // Direct Call
    func(x int) {
        fmt.Printf("Anonymous function: x=%d\n", x)
    }(42)
}

Saída:

TEXT 📖 Somente leitura
5
Anonymous function: x=42

(2) Closures: Captura de variáveis externas

Um closure = uma função + as variáveis externas às quais ela faz referência. Os closures permitem que as funções “lembrem” do ambiente em que foram criadas:

(3) ▶ Exemplo: Implementação de um contador usando closures

GO
package main

import "fmt"

// Returns a closure: increments by 1 on each call
func makeCounter() func() int {
    count := 0  // variable captured by closure
    return func() int {
        count++
        return count
    }
}

func main() {
    counter := makeCounter()
    fmt.Println(counter())  // 1
    fmt.Println(counter())  // 2
    fmt.Println(counter())  // 3

    // Each counter is an independent closure.
    another := makeCounter()
    fmt.Println(another())  // 1 (restarts counting)
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
1
2
3
1

(4) Closures x Funções comuns

Dimensão Função regular Fechamento
Com estado Sem estado Com estado (variáveis capturadas)
Memória Estática Sempre que uma nova instância é criada
Casos de uso Computação pura Fábricas, decoradores, callbacks

7. A função init e a inicialização do pacote

(1) Características da função init

Recurso Descrição
Sem parâmetros, sem valor de retorno func init()
Invocação automática Executada automaticamente quando o pacote é importado
Múltiplas Um pacote pode ter várias funções de inicialização (executadas na ordem em que são declaradas)
Antes da função main Executado antes da função main()

(2) ▶ Exemplo: Inicialização de pacote (Registro)

GO
// registry.go
package main

import "fmt"

var registry = make(map[string]int)

func init() {
    registry["version"] = 1
    registry["max_connections"] = 100
    fmt.Println("[init] registry initialized")
}

func init() {
    registry["debug"] = 1
    fmt.Println("[init] debug enabled")
}

func main() {
    fmt.Printf("Registry: %+v\n", registry)
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
[init] registry initialized
[init] debug enabled
Registry: map[debug:1 max_connections:100 version:1]

(3) init x main

Função Quando é chamada Finalidade
init() Executado automaticamente quando o pacote é importado Inicializa variáveis globais e registra drivers
main() Executado quando o programa é iniciado (uma vez) Ponto de entrada do programa

8. Funções como argumentos e valores de retorno (funções de ordem superior)

As funções em Go são cidadãos de primeira classe — elas podem ser atribuídas a variáveis, passadas como argumentos e retornadas como valores.

(1) Funções como argumentos (callbacks)

GO
package main

import "fmt"

// The second parameter is the function type: it accepts an `int` and returns an `int`.
func process(nums []int, callback func(int) int) []int {
    result := make([]int, len(nums))
    for i, n := range nums {
        result[i] = callback(n)
    }
    return result
}

func double(n int) int { return n * 2 }
func square(n int) int { return n * n }

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

    doubled := process(nums, double)
    fmt.Println("doubled:", doubled)

    squared := process(nums, square)
    fmt.Println("squared:", squared)
}

Saída:

TEXT 📖 Somente leitura
doubled: [2 4 6 8 10]
squared: [1 4 9 16 25]

(2) Funções como valores de retorno (fábrica)

GO
package main

import "fmt"

func makeAdder(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}

func main() {
    add10 := makeAdder(10)
    add100 := makeAdder(100)

    fmt.Println(add10(5))     // 15
    fmt.Println(add100(5))    // 105
}

Saída:

TEXT 📖 Somente leitura
15
105

(3) ▶ Exemplo: Tipos de função + funções de ordem superior (map/filter/reduce)

GO
package main

import "fmt"

func mapFunc(nums []int, f func(int) int) []int {
    result := make([]int, len(nums))
    for i, n := range nums {
        result[i] = f(n)
    }
    return result
}

func filterFunc(nums []int, predicate func(int) bool) []int {
    var result []int
    for _, n := range nums {
        if predicate(n) {
            result = append(result, n)
        }
    }
    return result
}

func reduceFunc(nums []int, initial int, f func(int, int) int) int {
    acc := initial
    for _, n := range nums {
        acc = f(acc, n)
    }
    return acc
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // 1. Map: Each element *2
    doubled := mapFunc(nums, func(n int) int { return n * 2 })

    // 2. Filter: Keep even numbers
    evens := filterFunc(nums, func(n int) bool { return n%2 == 0 })

    // 3. Reduce: Sum
    sum := reduceFunc(nums, 0, func(acc, n int) int { return acc + n })

    fmt.Printf("doubled: %v\n", doubled)
    fmt.Printf("evens: %v\n", evens)
    fmt.Printf("sum: %d\n", sum)
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
doubled: [2 4 6 8 10 12 14 16 18 20]
evens: [2 4 6 8 10]
sum: 55

9. Exemplo completo: Pipeline de processamento de dados

Combine todos os recursos da função para criar um pipeline de dados ETL (Extract-Transform-Load):

GO
// pipeline.go
package main

import (
    "fmt"
    "strings"
    "time"
)

// Data Sources
func extract() []string {
    return []string{
        "  Alice,28,Shanghai  ",
        "Bob,32,Beijing",
        "CHARLIE,45,Guangzhou",
        "",  // empty data
        "Dave,abc,ErrorCity",  // anomalous data
    }
}

// Step 1: Remove Spaces + Split Fields
func trim(s string) []string {
    return strings.Split(strings.TrimSpace(s), ",")
}

// Step 2: Convert to uppercase
func upper(s []string) []string {
    for i, v := range s {
        s[i] = strings.ToUpper(v)
    }
    return s
}

// Step 3: Verify the number of fields
func validate(s []string) (string, bool) {
    if len(s) != 3 || s[0] == "" {
        return "", false
    }
    return strings.Join(s, "|"), true
}

// Pipeline Function: Combining 3 Steps + Defer Report
func processPipeline(name string, data []string) (valid int, errors int) {
    defer func() {
        // Name the return value so that `defer` can modify the result
        fmt.Printf("[%s] Completed: valid=%d errors=%d duration=%v\n",
            name, valid, errors, time.Since(startTime))
    }()

    for _, raw := range data {
        s := trim(raw)
        if len(s) < 3 {
            errors++
            continue
        }
        s = upper(s)
        if result, ok := validate(s); ok {
            valid++
            fmt.Printf("  -> %s\n", result)
        } else {
            errors++
        }
    }
    return valid, errors
}

var startTime = time.Now()

func main() {
    fmt.Println("=== Start of Data Pipeline ===")

    // Combining Steps Using Functions as Arguments
    data := extract()
    valid, errors := processPipeline("ETL-1", data)

    fmt.Printf("\nSummary: valid=%d, errors=%d\n", valid, errors)
}

Resultado esperado:

TEXT 📖 Somente leitura
=== Start of Data Pipeline ===
  -> ALICE|28|SHANGHAI
  -> BOB|32|BEIJING
  -> CHARLIE|45|GUANGZHOU
  -> DAVE|ABC|ERRORCITY
[ETL-1] Completed: valid=4 errors=1 duration=2.5µs

Summary: valid=4, errors=1
🔥 Erro comum: startTime deve ser declarado no nível do arquivo (não é possível usar := dentro de init). Essa é a diferença sutil entre a função init e as declarações varvar está no nível do pacote, enquanto init está no nível da função.


❓ Perguntas Frequentes

P: As funções do Go suportam sobrecarga? R: Não, não suportam. O Go optou deliberadamente por não introduzir a sobrecarga de funções — para evitar a complexidade da “ambiguidade de sobrecarga” encontrada em C++ e Java. Se você precisar de um comportamento semelhante, use argumentos variáveis func foo(a ...int) ou nomes de função diferentes.

P: Qual é a diferença entre retornar vários valores e retornar uma estrutura? R: O retorno de vários valores é adequado para cenários que envolvem de 2 a 3 valores (especialmente um valor mais um erro), pois resulta em um código mais conciso; para mais de 3 valores, recomenda-se usar uma estrutura, já que as estruturas possuem nomes de campos, o que melhora a legibilidade.

P: Quais são as utilidades práticas de nomear valores de retorno? R: (1) As instruções automáticas return reduzem o código; (2) Os valores de retorno podem ser modificados em defer (Importante! Isso é usado no exemplo completo desta lição); (3) Documentação — as assinaturas de função servem como documentação.

P: Os closures podem causar vazamentos de memória? R: Sim. O tempo de vida das variáveis capturadas por um closure é prolongado até que o próprio closure termine. Manter um closure por um longo período (como em um cache ou dentro de uma goroutine) impede que as variáveis sejam coletadas pelo coletor de lixo.

P: A função init pode ser chamada manualmente? R: Não. A função init só pode ser chamada automaticamente pelo runtime do Go, e a função init de cada pacote é executada apenas uma vez. Tentar chamar a função init manualmente resultará em um erro de compilação.

P: Qual é a diferença entre argumentos variádicos e argumentos de fatia? R: Um argumento variádico nums ...int é, essencialmente, uma []int fatia dentro da função, mas as convenções de chamada diferem: foo(1,2,3) versus foo([]int{1,2,3}...). O primeiro é um recurso sintático.

P: O uso de funções como valores acarreta uma perda de desempenho? R: Os compiladores modernos otimizam os valores das funções (chamando-os diretamente), portanto, praticamente não há sobrecarga. No entanto, armazenar uma função em um mapa ou fatia e chamá-la com frequência resultará em uma leve sobrecarga devido ao endereçamento indireto — o que geralmente é insignificante.


📖 Resumo


📝 Exercícios

  1. Problema básico (Dificuldade ⭐): Escreva uma função max(nums ...int) int que retorne o valor máximo entre todos os argumentos. A função deve usar argumentos variáveis; ao chamar max(1, 5, 3, 9, 2), o resultado deve ser 9.

  2. Problema avançado (Dificuldade ⭐⭐): Implemente um makeBankAccount(initial int) (deposit func(int) int, withdraw func(int) (int, bool), balance func() int) para simular uma conta bancária: deposit aumenta o saldo e retorna o novo saldo; withdraw deduz fundos (retorna false se o saldo for insuficiente); balance consulta o saldo. Você deve usar closures.

  3. Problema de Desafio (Dificuldade ⭐⭐⭐): Implemente um pipeline de dados funcional: Dada uma entrada []string (uma sequência de números), use funções de ordem superior para executar sequencialmente as quatro etapas parse -> filter(>10) -> map(*2) -> sum. Cada etapa deve ser uma função independente, e as funções combinadas devem produzir o resultado final. Por exemplo: ["1", "15", "3", "20"]parse [1,15,3,20]filter>10 [15,20]map*2 [30,40]sum 70.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%