Backend

Go

Referência completa de Go cobrindo variáveis, tipos, funções, concorrência, generics, HTTP, JSON, banco de dados e testes

Variáveis: var, :=, múltiplas declarações, zero values, constantes e iota

Go possui duas formas de declarar variáveis. var é explícita e funciona dentro ou fora de funções. := é o operador de declaração curta, disponível apenas dentro de funções — ele infere o tipo automaticamente. Variáveis não inicializadas recebem o zero value do tipo: 0 para números, "" para strings, false para bool e nil para ponteiros, slices, maps, channels e interfaces.

// var com tipo explícito
var name string = "Rafael"
var count int    // zero value: 0

// var sem tipo — inferência a partir do valor
var total = 99.90

// Declaração curta — mais comum dentro de funções
price := 29.90
status := "pending"

// Múltiplas variáveis na mesma linha
id, err := db.Insert(order)

// Bloco var — agrupa declarações relacionadas
var (
    host = "localhost"
    port = 5432
    ssl  = false
)

// Constantes — avaliadas em tempo de compilação
const MaxRetries = 3
const DefaultTimeout = 30 * time.Second

// iota — contador automático dentro de bloco const
type OrderStatus int

const (
    StatusPending   OrderStatus = iota // 0
    StatusConfirmed                    // 1
    StatusShipped                      // 2
    StatusDelivered                    // 3
    StatusCancelled                    // 4
)

// iota com operações binárias — flags de permissão
type Permission uint

const (
    Read    Permission = 1 << iota // 1
    Write                          // 2
    Execute                        // 4
)

Tipos: int, float64, string, bool, byte/rune, conversões explícitas

Go é estaticamente tipado e não faz conversões implícitas. Misturar int com int64, ou float32 com float64, gera erro de compilação. Toda conversão deve ser explícita com T(valor). byte é alias de uint8 e rune é alias de int32 (representa um codepoint Unicode).

// Tipos inteiros — tamanho explícito ou dependente da plataforma
var a int     = 42       // 32 ou 64 bits conforme o OS
var b int64   = 42
var c uint32  = 42

// Ponto flutuante
var price float64 = 29.90
var ratio float32 = 0.75

// String é imutável e sequência de bytes UTF-8
name := "Rafael"
fmt.Println(len(name))     // bytes, não caracteres
fmt.Println(name[0])       // byte: 82 (código ASCII de 'R')

// byte e rune
var b byte = 'A'           // uint8 = 65
var r rune = ''           // int32 = 8364 (codepoint Unicode)

// Iterar por rune (não por byte) para Unicode correto
for i, r := range "café" {
    fmt.Printf("índice %d: %c (%d)\n", i, r, r)
}

// Conversões explícitas — obrigatórias
count := 5
total := 99.90
perItem := total / float64(count)  // int → float64 explícito

// Conversão string ↔ []byte
s := "hello"
b2 := []byte(s)   // copia os bytes
s2 := string(b2)  // de volta para string

// strconv — conversão entre tipos e string
import "strconv"
n, err := strconv.Atoi("42")       // string → int
s3 := strconv.Itoa(42)             // int → string
f, err := strconv.ParseFloat("3.14", 64)

Funções: múltiplos retornos, named returns, variadic, first-class functions

Funções em Go são cidadãs de primeira classe: podem ser atribuídas a variáveis, passadas como parâmetros e retornadas de outras funções. A convenção de retornar (resultado, error) é o padrão de tratamento de erros da linguagem.

// Múltiplos retornos — convenção (valor, erro)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("divisão por zero")
    }
    return a / b, nil
}

result, err := divide(10, 3)
if err != nil {
    log.Fatal(err)
}

// Named returns — nomeiam o resultado e permitem return nu
// Use com moderação: útil para deixar claro o que está sendo retornado
func minMax(nums []float64) (min, max float64) {
    min, max = nums[0], nums[0]
    for _, n := range nums[1:] {
        if n < min { min = n }
        if n > max { max = n }
    }
    return // return nu: retorna min e max pelo nome
}

// Variadic — aceita zero ou mais argumentos do mesmo tipo
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

sum(1, 2, 3)         // chamada direta
nums := []int{1, 2, 3}
sum(nums...)         // expande slice como argumentos variadic

// First-class functions — atribuir, passar, retornar
type FilterFn func(order Order) bool

func filterOrders(orders []Order, fn FilterFn) []Order {
    result := make([]Order, 0)
    for _, o := range orders {
        if fn(o) {
            result = append(result, o)
        }
    }
    return result
}

// Função anônima (closure) captura variáveis do escopo externo
minTotal := 100.0
expensive := filterOrders(orders, func(o Order) bool {
    return o.Total >= minTotal
})

// Retornar função — factory pattern
func newMultiplier(factor float64) func(float64) float64 {
    return func(x float64) float64 {
        return x * factor
    }
}
double := newMultiplier(2.0)
fmt.Println(double(50.0)) // 100

Controle de fluxo: if com inicialização, switch, for

Go simplifica o fluxo de controle. O if aceita um statement de inicialização separado por ;, que cria uma variável com escopo limitado ao bloco. O switch não precisa de break entre cases (não há fallthrough por padrão). O for é o único loop da linguagem — cobre os casos de while e foreach.

// if com inicialização — variável vive apenas no if/else
if err := doSomething(); err != nil {
    log.Printf("erro: %v", err)
    return err
}

// Padrão comum: verificar erro com variável de escopo restrito
if user, err := findUser(id); err != nil {
    return nil, fmt.Errorf("usuário %s não encontrado: %w", id, err)
} else {
    return user, nil
}

// switch — avalia cases de cima para baixo, para no primeiro match
switch order.Status {
case "pending":
    process(order)
case "confirmed", "shipped": // múltiplos valores no mesmo case
    notify(order)
case "cancelled":
    refund(order)
default:
    log.Printf("status desconhecido: %s", order.Status)
}

// switch sem expressão — substitui if/else if encadeado
switch {
case order.Total > 500:
    applyDiscount(order, 0.1)
case order.Total > 200:
    applyDiscount(order, 0.05)
}

// Type switch — inspeciona o tipo dinâmico de uma interface
func describe(v any) string {
    switch t := v.(type) {
    case int:
        return fmt.Sprintf("inteiro: %d", t)
    case string:
        return fmt.Sprintf("string: %q", t)
    case []int:
        return fmt.Sprintf("slice de int com %d elementos", len(t))
    default:
        return fmt.Sprintf("tipo desconhecido: %T", t)
    }
}

// for clássico
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// for estilo while
for order.Status != "delivered" {
    checkStatus(order)
}

// for infinito com break
for {
    msg, ok := <-ch
    if !ok {
        break
    }
    process(msg)
}

// range sobre slice
for i, item := range items {
    fmt.Printf("%d: %v\n", i, item)
}

// range sobre map
for key, value := range priceMap {
    fmt.Printf("%s%.2f\n", key, value)
}

// range sobre inteiro (Go 1.22+)
for i := range 5 { // 0, 1, 2, 3, 4
    fmt.Println(i)
}

Arrays e Slices: criação, append, copy, 3-index, slice tricks

Arrays têm tamanho fixo definido em tempo de compilação. Slices são a estrutura primária — uma visão dinâmica e redimensionável de um array subjacente, com três campos internos: ponteiro para os dados, len (tamanho atual) e cap (capacidade alocada). append retorna um novo slice e pode realocar o array quando a capacidade é excedida.

// Array — tamanho fixo, faz parte do tipo: [3]int ≠ [4]int
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{10, 20, 30} // ... infere o tamanho

// Slice — prefira slices a arrays na maioria dos casos
s := []string{"pending", "confirmed", "shipped"}

// make — aloca com tamanho e capacidade definidos
orders := make([]Order, 0, 100) // len=0, cap=100

// append — retorna novo slice (pode realocar)
orders = append(orders, Order{ID: "1"})
orders = append(orders, Order{ID: "2"}, Order{ID: "3"}) // múltiplos
other := []Order{{ID: "4"}, {ID: "5"}}
orders = append(orders, other...) // expande slice

// Fatiamento — [inicio:fim], fim é exclusivo
all := []int{0, 1, 2, 3, 4, 5}
first3  := all[:3]   // [0 1 2]
last3   := all[3:]   // [3 4 5]
middle  := all[1:4]  // [1 2 3]

// copy — copia elementos entre slices independentes
dst := make([]int, len(all))
n := copy(dst, all) // retorna número de elementos copiados

// 3-index slice — controla a capacidade do slice resultante
// all[low:high:max] — cap do resultado = max - low
limited := all[1:3:4] // len=2, cap=3 (não "vaza" elementos além do índice 4)

// Slice tricks úteis

// Filtrar in-place sem alocar
func filterPaid(orders []Order) []Order {
    result := orders[:0]
    for _, o := range orders {
        if o.Status == "paid" {
            result = append(result, o)
        }
    }
    return result
}

// Reverter slice
func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

// Remover elemento no índice i (sem preservar ordem)
func removeUnordered(s []Order, i int) []Order {
    s[i] = s[len(s)-1]
    return s[:len(s)-1]
}

// Remover elemento no índice i (preservando ordem)
func removeOrdered(s []Order, i int) []Order {
    return append(s[:i], s[i+1:]...)
}

Maps: criação, acesso seguro, iteração, delete, maps de slices

Map é o tipo nativo de hash map. A leitura de uma chave inexistente retorna o zero value do tipo do valor — nunca um erro. Para distinguir “chave não existe” de “valor é zero”, use o padrão comma ok. Maps não são safe para uso concorrente — use sync.Map ou sync.RWMutex se necessário.

// Criação — make é necessário antes de usar
prices := make(map[string]float64)

// Literal — inicialização com valores
config := map[string]string{
    "host": "localhost",
    "port": "5432",
}

// Atribuição e leitura
prices["SKU-001"] = 29.90
p := prices["SKU-001"]      // 29.90
p2 := prices["SKU-999"]     // 0.0 — zero value, chave não existe

// Comma ok — acesso seguro
price, ok := prices["SKU-001"]
if !ok {
    return fmt.Errorf("produto não encontrado")
}

// Delete
delete(prices, "SKU-001")

// Iterar — ordem não é garantida
for sku, price := range prices {
    fmt.Printf("%s: R$ %.2f\n", sku, price)
}

// Verificar se map tem chave (sem usar o valor)
if _, exists := prices["SKU-001"]; exists {
    fmt.Println("produto existe")
}

// Map de slices — agrupar pedidos por status
byStatus := make(map[string][]Order)
for _, o := range orders {
    byStatus[o.Status] = append(byStatus[o.Status], o)
}

// Map como set (conjunto) — usar struct{} como valor (0 bytes)
seen := make(map[string]struct{})
for _, id := range ids {
    seen[id] = struct{}{}
}
if _, ok := seen["order-42"]; ok {
    // já processado
}

// len retorna o número de chaves
fmt.Println(len(prices))

Structs: definição, embedding, anonymous structs, struct tags, comparação

Structs são os tipos compostos fundamentais de Go. Não há classes — structs com métodos substituem esse papel. Embedding permite composição sem herança: o tipo embedado tem seus campos e métodos promovidos ao tipo externo. Struct tags são metadados lidos em runtime via reflect, usados extensivamente por encoding/json, ORMs e validadores.

// Struct básica
type Address struct {
    Street  string
    City    string
    ZipCode string
    Country string
}

type Customer struct {
    ID      string
    Name    string
    Email   string
    Address Address // campo do tipo Address (aninhado, não embedado)
}

// Embedding — Address é promovida a Customer
type CustomerEmbed struct {
    ID    string
    Name  string
    Address            // sem nome de campo — embedding
}

c := CustomerEmbed{Address: Address{City: "São Paulo"}}
fmt.Println(c.City) // acesso direto via promoção

// Struct tags — metadados para bibliotecas externas
type Product struct {
    ID          string  `json:"id"             db:"product_id"`
    Name        string  `json:"name"           db:"name"         validate:"required"`
    Price       float64 `json:"price"          db:"price"        validate:"gte=0"`
    Description string  `json:"description,omitempty"` // omite se vazio
    InternalRef string  `json:"-"`             // ignora no JSON
}

// Anonymous struct — útil para testes e respostas de API ad hoc
response := struct {
    Status  string `json:"status"`
    OrderID string `json:"order_id"`
}{
    Status:  "created",
    OrderID: "ord-123",
}

// Comparação — structs são comparáveis se todos os campos forem comparáveis
// (sem slices, maps ou functions como campos)
type Money struct {
    Amount   float64
    Currency string
}

m1 := Money{100.0, "BRL"}
m2 := Money{100.0, "BRL"}
fmt.Println(m1 == m2) // true

// Inicialização posicional vs por nome
p1 := Product{"id-1", "Camiseta", 59.90, "Desc", "REF"} // frágil
p2 := Product{ID: "id-1", Name: "Camiseta", Price: 59.90} // recomendado

Interfaces: implícita, composição, any, type assertion, type switch

Go usa structural typing: um tipo satisfaz uma interface automaticamente se implementar todos os seus métodos — sem implements. Isso desacopla a definição do tipo da interface que ele satisfaz. Interfaces devem ser pequenas; a preferência idiomática é de 1 a 3 métodos.

// Interface simples — convenção: nome do método + "er"
type Notifier interface {
    Notify(ctx context.Context, msg string) error
}

// Composição de interfaces
type ReadWriter interface {
    Reader // embeds io.Reader
    Writer // embeds io.Writer
}

type PaymentProcessor interface {
    Charge(ctx context.Context, amount float64) (string, error)
    Refund(ctx context.Context, chargeID string) error
}

// StripeProcessor satisfaz PaymentProcessor implicitamente
type StripeProcessor struct{ apiKey string }

func (s *StripeProcessor) Charge(ctx context.Context, amount float64) (string, error) {
    // chama Stripe API...
    return "ch_stripe_123", nil
}
func (s *StripeProcessor) Refund(ctx context.Context, chargeID string) error {
    return nil
}

// Interface vazia — any (alias de interface{} desde Go 1.18)
func printAnything(v any) {
    fmt.Printf("%T: %v\n", v, v)
}

// Type assertion — extrai o tipo concreto de uma interface
var p PaymentProcessor = &StripeProcessor{apiKey: "sk_test"}

stripe, ok := p.(*StripeProcessor) // comma ok — seguro
if ok {
    fmt.Println(stripe.apiKey)
}

// Sem comma ok — panic se o tipo não bater
stripe2 := p.(*StripeProcessor) // panic se p não for *StripeProcessor

// Type switch — já demonstrado em controle de fluxo (seção acima)
// Interface nil — interface com tipo mas valor nil: armadilha clássica
var proc *StripeProcessor      // nil do tipo concreto
var iface PaymentProcessor = proc // iface NÃO é nil (tem tipo, mas valor nil)
fmt.Println(iface == nil)     // false — armadilha!

// Correto: nunca atribua ponteiro nil a uma interface diretamente
// Use retorno nil explícito do tipo interface
func newProcessor(useStripe bool) PaymentProcessor {
    if !useStripe {
        return nil // nil de interface — correto
    }
    return &StripeProcessor{}
}

Ponteiros: & e *, nil, pass-by-value vs pass-by-pointer

Go passa tudo por valor. Ponteiros permitem que uma função modifique o valor original ou evite cópias caras. &x retorna o endereço de x. *p acessa o valor no endereço. O zero value de qualquer ponteiro é nil.

// & e * básico
x := 42
p := &x         // p é *int — armazena o endereço de x
*p = 100        // altera x via ponteiro
fmt.Println(x)  // 100

// nil pointer — causa panic se desreferenciado
var ptr *Order
if ptr != nil {
    fmt.Println(ptr.ID) // seguro
}

// Pass by value — a função recebe uma cópia
func applyDiscountValue(o Order, pct float64) Order {
    o.Total *= (1 - pct) // modifica a cópia
    return o             // retorna a cópia modificada
}

// Pass by pointer — a função modifica o original
func applyDiscountPtr(o *Order, pct float64) {
    o.Total *= (1 - pct)
}

order := Order{Total: 100.0}
applyDiscountPtr(&order, 0.1)
fmt.Println(order.Total) // 90.0

// Pointer to struct — Go faz auto-dereference nos campos
o := &Order{Status: "pending"}
fmt.Println(o.Status)  // Go desreferencia automaticamente: (*o).Status
o.Status = "confirmed" // equivalente a (*o).Status = "confirmed"

Methods: value vs pointer receivers — regras e convenções

Um method é uma função com um parâmetro receiver especial. O receiver é apenas um parâmetro — a mesma semântica de cópia se aplica. A convenção da documentação oficial: se qualquer método do tipo usa pointer receiver, use pointer receivers em todos os métodos do tipo.

type Cart struct {
    Items    []Item
    Discount float64
}

// Pointer receiver — necessário quando o método modifica o struct
func (c *Cart) AddItem(item Item) {
    c.Items = append(c.Items, item)
}

func (c *Cart) ApplyDiscount(pct float64) {
    c.Discount = pct
}

// Value receiver — OK para leitura, trabalha com cópia
func (c Cart) Total() float64 {
    subtotal := 0.0
    for _, item := range c.Items {
        subtotal += item.Price * float64(item.Qty)
    }
    return subtotal * (1 - c.Discount)
}

// Regras:
// 1. Modifica o receiver → pointer receiver (obrigatório)
// 2. Struct grande → pointer receiver (evita cópia cara)
// 3. Receiver pode ser nil → pointer receiver
// 4. Tipo primitivo ou struct pequena imutável → value receiver OK
// 5. Consistência: misturar no mesmo tipo confunde — escolha um e mantenha

// Go converte automaticamente entre *T e T ao chamar métodos
cart := Cart{}
cart.AddItem(Item{Name: "Camiseta", Price: 59.90, Qty: 2}) // Go converte para (&cart).AddItem

ptr := &Cart{}
fmt.Println(ptr.Total()) // Go converte para (*ptr).Total()

Goroutines: go keyword, WaitGroup, patterns de concorrência

Goroutines são threads cooperativas gerenciadas pelo runtime do Go. São baratas — começam com apenas ~2KB de stack, crescendo sob demanda. Lançar mil goroutines é comum. O sync.WaitGroup coordena espera por grupos de goroutines.

// go keyword — lança goroutine
go func() {
    fmt.Println("executando em paralelo")
}()

// WaitGroup — aguarda N goroutines terminarem
func processOrdersConcurrently(orders []Order) {
    var wg sync.WaitGroup

    for _, order := range orders {
        wg.Add(1)
        go func(o Order) { // captura por parâmetro — boa prática
            defer wg.Done()
            processOrder(o)
        }(order)
    }

    wg.Wait() // bloqueia até todas terminarem
}

// Fan-out + Fan-in — distribui trabalho e coleta resultados
func fanOut(ctx context.Context, orders []Order) []Result {
    results := make(chan Result, len(orders))
    var wg sync.WaitGroup

    for _, o := range orders {
        wg.Add(1)
        go func(ord Order) {
            defer wg.Done()
            result := processOrder(ctx, ord)
            results <- result
        }(o)
    }

    // Fecha o channel quando todas terminarem
    go func() {
        wg.Wait()
        close(results)
    }()

    // Coleta todos os resultados
    var out []Result
    for r := range results {
        out = append(out, r)
    }
    return out
}

// Worker pool — limita o número de goroutines simultâneas
func workerPool(ctx context.Context, orders []Order, workers int) {
    jobs := make(chan Order, len(orders))
    var wg sync.WaitGroup

    // Lança workers
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for o := range jobs {
                processOrder(ctx, o)
            }
        }()
    }

    // Envia trabalho
    for _, o := range orders {
        jobs <- o
    }
    close(jobs)
    wg.Wait()
}

Channels: buffered vs unbuffered, close, range, select

Channels são o mecanismo primário de comunicação entre goroutines. Unbuffered: send bloqueia até alguém receber (sincronização). Buffered: send bloqueia apenas se o buffer estiver cheio. close sinaliza que não haverá mais envios — receptores podem detectar isso.

// Unbuffered — sincronização ponto-a-ponto
ch := make(chan string)
go func() {
    ch <- "processado" // bloqueia até alguém receber
}()
msg := <-ch // bloqueia até ter algo

// Buffered — send não bloqueia até capacidade ser atingida
buffered := make(chan Order, 10)
buffered <- Order{ID: "1"} // não bloqueia (buffer tem espaço)

// close + range — iterar até o channel ser fechado
done := make(chan struct{})

go func() {
    for item := range ch { // para quando ch for fechado
        process(item)
    }
    close(done)
}()

close(ch) // sinaliza que não há mais itens
<-done    // aguarda a goroutine terminar

// select — espera o primeiro channel pronto
func fetchWithTimeout(ctx context.Context, id string) (*Order, error) {
    resultCh := make(chan *Order, 1)
    errCh    := make(chan error, 1)

    go func() {
        order, err := fetchOrder(id)
        if err != nil {
            errCh <- err
            return
        }
        resultCh <- order
    }()

    select {
    case order := <-resultCh:
        return order, nil
    case err := <-errCh:
        return nil, err
    case <-ctx.Done():
        return nil, ctx.Err()
    }
}

// select com default — não bloqueia
select {
case msg := <-ch:
    process(msg)
default:
    // channel estava vazio — não bloqueia
}

// Direção de channel em parâmetros — restringe send/receive
func producer(out chan<- Order) { out <- Order{} } // só envia
func consumer(in <-chan Order)  { o := <-in; _ = o } // só recebe

Concorrência avançada: Mutex, RWMutex, atomic, sync.Once, errgroup

Para estado compartilhado entre goroutines, o sync.Mutex garante exclusão mútua. RWMutex permite múltiplas leituras simultâneas mas escrita exclusiva. sync/atomic oferece operações atômicas sem lock para tipos simples. errgroup combina WaitGroup com propagação de erros.

// Mutex — protege estado compartilhado
type SafeCache struct {
    mu    sync.Mutex
    items map[string]Order
}

func (c *SafeCache) Set(key string, order Order) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.items[key] = order
}

func (c *SafeCache) Get(key string) (Order, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()
    o, ok := c.items[key]
    return o, ok
}

// RWMutex — leituras concorrentes, escrita exclusiva
type Cache struct {
    mu    sync.RWMutex
    items map[string]Order
}

func (c *Cache) Get(key string) (Order, bool) {
    c.mu.RLock()         // múltiplos leitores simultâneos
    defer c.mu.RUnlock()
    o, ok := c.items[key]
    return o, ok
}

func (c *Cache) Set(key string, o Order) {
    c.mu.Lock()          // escrita exclusiva
    defer c.mu.Unlock()
    c.items[key] = o
}

// sync/atomic — operações atômicas sem lock (para tipos simples)
var requestCount int64
atomic.AddInt64(&requestCount, 1)
total := atomic.LoadInt64(&requestCount)

// sync.Once — executa uma função exatamente uma vez (singleton)
var (
    instance *DB
    once     sync.Once
)

func GetDB() *DB {
    once.Do(func() {
        instance = openDB()
    })
    return instance
}

// errgroup — WaitGroup com propagação de erros (golang.org/x/sync)
import "golang.org/x/sync/errgroup"

func fetchAll(ctx context.Context, ids []string) ([]Order, error) {
    g, ctx := errgroup.WithContext(ctx)
    results := make([]Order, len(ids))

    for i, id := range ids {
        i, id := i, id // captura para Go < 1.22
        g.Go(func() error {
            o, err := fetchOrder(ctx, id)
            if err != nil {
                return fmt.Errorf("order %s: %w", id, err)
            }
            results[i] = o
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

Context: cancelamento, deadline, timeout, propagação de valores

context.Context carrega deadlines, sinais de cancelamento e valores entre fronteiras de API. A regra: passe ctx como primeiro parâmetro em toda função que faça I/O, acesse banco ou chame serviços externos. Sempre chame cancel() para liberar recursos.

// Background e TODO — raízes de context
ctx := context.Background() // contexto raiz (main, testes, top-level)
ctx2 := context.TODO()      // placeholder quando o context adequado ainda não foi definido

// WithCancel — cancela manualmente
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // sempre

go func() {
    // Monitora alguma condição
    if criticalError() {
        cancel() // cancela o context e todas as goroutines que o usam
    }
}()

// WithTimeout — cancela automaticamente após duração
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

rows, err := db.QueryContext(ctx, "SELECT * FROM orders WHERE customer_id = $1", id)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return nil, fmt.Errorf("query excedeu timeout de 5s")
    }
    return nil, err
}

// WithDeadline — cancela em um momento absoluto
deadline := time.Now().Add(30 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()

// WithValue — propaga valores (request-scoped: trace ID, user ID)
// Use tipos privados como chave para evitar colisões
type contextKey string
const userIDKey contextKey = "userID"

func withUserID(ctx context.Context, userID string) context.Context {
    return context.WithValue(ctx, userIDKey, userID)
}

func getUserID(ctx context.Context) (string, bool) {
    id, ok := ctx.Value(userIDKey).(string)
    return id, ok
}

// Verificar cancelamento em loop longo
func processLargeDataset(ctx context.Context, records []Record) error {
    for _, rec := range records {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            processRecord(rec)
        }
    }
    return nil
}

Error handling: errors.New, fmt.Errorf %w, errors.Is, errors.As, tipos customizados

Go não tem exceções. Erros são valores do tipo error. A cadeia de wrapping com %w permite que errors.Is e errors.As inspecionem erros aninhados em qualquer profundidade. Erros sentinela são variáveis exportadas que identificam condições de erro específicas.

// Erros sentinela — variáveis exportadas para comparação com errors.Is
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrConflict     = errors.New("conflict")
)

// Tipo de erro customizado — carrega dados estruturados
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validação falhou em '%s': %s", e.Field, e.Message)
}

// Tipo de erro para recursos não encontrados
type NotFoundError struct {
    Resource string
    ID       string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s com ID '%s' não encontrado", e.Resource, e.ID)
}

// Wrapping com %w — preserva o erro original na cadeia
func findOrder(ctx context.Context, id string) (*Order, error) {
    row := db.QueryRowContext(ctx, "SELECT * FROM orders WHERE id=$1", id)
    order := &Order{}
    if err := row.Scan(&order.ID, &order.Status, &order.Total); err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, &NotFoundError{Resource: "order", ID: id}
        }
        return nil, fmt.Errorf("findOrder %s: %w", id, err) // adiciona contexto
    }
    return order, nil
}

// errors.Is — verifica identidade do erro em qualquer profundidade da cadeia
err := findOrder(ctx, "missing-id")
if errors.Is(err, ErrNotFound) { ... }      // sentinela
if errors.Is(err, context.Canceled) { ... } // context cancelado

// errors.As — extrai tipo específico da cadeia
var notFound *NotFoundError
if errors.As(err, &notFound) {
    log.Printf("recurso %s com id %s não encontrado", notFound.Resource, notFound.ID)
    return http.StatusNotFound, nil
}

var valErr *ValidationError
if errors.As(err, &valErr) {
    return http.StatusBadRequest, map[string]string{"field": valErr.Field}
}

// errors.Join (Go 1.20+) — combina múltiplos erros
func validateOrder(o Order) error {
    var errs []error
    if o.Total <= 0 {
        errs = append(errs, &ValidationError{Field: "total", Message: "deve ser positivo"})
    }
    if o.CustomerID == "" {
        errs = append(errs, &ValidationError{Field: "customer_id", Message: "obrigatório"})
    }
    return errors.Join(errs...) // nil se errs estiver vazio
}

Defer: LIFO, closures, recover de panics

defer agenda uma chamada de função para quando a função corrente retornar. Múltiplos defers executam em ordem LIFO (último a ser declarado, primeiro a executar). Defers capturam os valores dos parâmetros no momento da declaração, mas closures capturam variáveis por referência.

// Cleanup garantido — padrão abrir/fechar
func readFile(path string) ([]byte, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer f.Close() // executado quando readFile retornar, independente do erro

    return io.ReadAll(f)
}

// LIFO — múltiplos defers
func example() {
    defer fmt.Println("terceiro")  // executa terceiro
    defer fmt.Println("segundo")   // executa segundo
    defer fmt.Println("primeiro")  // executa primeiro
    fmt.Println("função")
}
// saída: função, primeiro, segundo, terceiro

// Defer com closure — captura variável por referência (named return)
func counted() (result int, err error) {
    defer func() {
        if err != nil {
            log.Printf("counted falhou após resultado %d: %v", result, err)
        }
    }()
    result, err = compute()
    return
}

// Recover — captura um panic e evita crash da goroutine
func safeHandler(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recuperado: %v\n%s", r, debug.Stack())
        }
    }()
    fn()
    return nil
}

// Middleware HTTP com recover
func recoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if rec := recover(); rec != nil {
                log.Printf("panic: %v\n%s", rec, debug.Stack())
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

Packages e Modules: go.mod, import, export, init()

Go organiza código em packages. Um módulo (go.mod) agrupa packages com um nome de módulo e define as dependências. Identificadores exportados começam com letra maiúscula. A função init() executa automaticamente ao importar o package — use com parcimônia.

// go.mod — arquivo raiz do módulo
// module github.com/rafael/minha-api
// go 1.23
// require (
//     github.com/lib/pq v1.10.9
//     golang.org/x/sync v0.7.0
// )

// Exportado — acessível fora do package (maiúscula)
func ProcessOrder(o Order) error { ... }
type OrderService struct { ... }
var DefaultTimeout = 30 * time.Second

// Não exportado — interno ao package (minúscula)
func validateAmount(amount float64) bool { ... }
type internalCache struct { ... }

// import — caminho do módulo + package
import (
    "context"
    "fmt"

    "github.com/lib/pq"                      // driver PostgreSQL
    "github.com/rafael/minha-api/internal/order" // package interno
)

// Import com alias — evita colisão de nomes
import (
    mrand "math/rand"
    crand "crypto/rand"
)

// Blank import — executa init() do package para efeito colateral
import _ "github.com/lib/pq" // registra driver pq sem usar o package diretamente

// init() — inicialização automática do package
// Pode haver múltiplos init() no mesmo arquivo e package
func init() {
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}

// Comandos de módulo
// go mod init github.com/user/projeto  — cria go.mod
// go get github.com/pkg/errors@v0.9.1  — adiciona dependência
// go mod tidy                          — remove deps não usadas
// go mod download                      — baixa deps para cache local
// go work init ./api ./lib             — cria workspace (go.work) para monorepo

Testing: testing.T, table-driven tests, benchmarks, testify

Go tem suporte a testes na stdlib sem framework externo. A convenção: arquivo *_test.go, funções Test*, Benchmark* e Example*. Table-driven tests são o padrão idiomático para cobrir múltiplos casos com pouca repetição.

// order_test.go
package order_test

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

// Table-driven test — o padrão idiomático
func TestDivide(t *testing.T) {
    tests := []struct {
        name    string
        a, b    float64
        want    float64
        wantErr bool
    }{
        {"divisão normal",    10, 2, 5.0,  false},
        {"divisão por zero",  10, 0, 0,    true},
        {"números negativos", -6, 3, -2.0, false},
    }

    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            got, err := Divide(tc.a, tc.b)
            if tc.wantErr {
                require.Error(t, err)
                return
            }
            require.NoError(t, err)
            assert.Equal(t, tc.want, got)
        })
    }
}

// t.Helper — marca função auxiliar (erros mostram o local do chamador)
func assertOrderPaid(t *testing.T, o Order) {
    t.Helper()
    assert.Equal(t, "paid", o.Status, "order deveria estar paga")
}

// Subtests com t.Run — pode ser filtrado com -run TestOrder/paid
func TestOrderLifecycle(t *testing.T) {
    t.Run("confirm order", func(t *testing.T) { ... })
    t.Run("pay order", func(t *testing.T) { ... })
}

// Benchmark — mede performance
func BenchmarkProcessOrder(b *testing.B) {
    order := NewTestOrder()
    b.ResetTimer() // descarta o custo de setup do timer

    for i := 0; i < b.N; i++ {
        ProcessOrder(order)
    }
}

// go test -bench=. -benchmem -count=3 ./...

// TestMain — setup/teardown global para o package de testes
func TestMain(m *testing.M) {
    setup()
    code := m.Run()
    teardown()
    os.Exit(code)
}

Generics (Go 1.18+): type parameters, constraints, comparable, any

Generics permitem escrever funções e tipos que funcionam com múltiplos tipos sem repetição. O parâmetro de tipo fica entre colchetes [T Constraint]. comparable é a constraint embutida para tipos que suportam == e !=. any é alias de interface{}.

// Função genérica — T é o tipo parameter, any é a constraint (qualquer tipo)
func Map[T, U any](s []T, fn func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = fn(v)
    }
    return result
}

// Filter genérico
func Filter[T any](s []T, fn func(T) bool) []T {
    var result []T
    for _, v := range s {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

// Uso
prices := []float64{10.0, 50.0, 200.0}
doubled := Map(prices, func(p float64) float64 { return p * 2 })

orderIDs := Map(orders, func(o Order) string { return o.ID })

// Constraints com ~T — aceita tipos baseados em T
type Number interface {
    ~int | ~int64 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

Sum([]int{1, 2, 3})
Sum([]float64{1.1, 2.2})

// comparable — tipos que suportam == (mapas, sets)
func Contains[T comparable](s []T, v T) bool {
    for _, item := range s {
        if item == v {
            return true
        }
    }
    return false
}

// Tipo genérico — estrutura de dados reutilizável
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T)        { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

stack := Stack[Order]{}
stack.Push(Order{ID: "1"})
o, ok := stack.Pop()

HTTP com net/http: servidor, cliente, middleware pattern, mux

A stdlib net/http é suficiente para APIs de produção. Go 1.22 melhorou o ServeMux padrão com suporte a métodos HTTP e path parameters. O padrão middleware é uma função que recebe e retorna http.Handler.

// Servidor básico com mux embutido
mux := http.NewServeMux()

// Go 1.22+ — método HTTP + path parameter direto no mux
mux.HandleFunc("GET /orders", listOrders)
mux.HandleFunc("POST /orders", createOrder)
mux.HandleFunc("GET /orders/{id}", getOrder)
mux.HandleFunc("DELETE /orders/{id}", deleteOrder)

srv := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    ReadTimeout:  10 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout:  60 * time.Second,
}
log.Fatal(srv.ListenAndServe())

// Handler — extrai path parameter (Go 1.22+)
func getOrder(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    order, err := orderService.FindByID(r.Context(), id)
    if err != nil {
        http.Error(w, err.Error(), http.StatusNotFound)
        return
    }
    json.NewEncoder(w).Encode(order)
}

// Middleware — função que envolve um Handler
type Middleware func(http.Handler) http.Handler

func logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func authRequired(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if !validateToken(token) {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// Encadeamento de middlewares
handler := logging(authRequired(mux))

// Cliente HTTP com timeout
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := client.Do(req)
if err != nil {
    return nil, fmt.Errorf("http get: %w", err)
}
defer resp.Body.Close()

Encoding/JSON: Marshal, Unmarshal, struct tags, custom marshaler

O package encoding/json serializa/desserializa JSON via struct tags. Para comportamento customizado, implemente json.Marshaler e json.Unmarshaler.

import "encoding/json"

// Struct com tags JSON
type Product struct {
    ID          string   `json:"id"`
    Name        string   `json:"name"`
    Price       float64  `json:"price"`
    Tags        []string `json:"tags,omitempty"`     // omite se nil/vazio
    InternalRef string   `json:"-"`                  // nunca serializado
}

// Marshal — struct → JSON
p := Product{ID: "p-1", Name: "Camiseta", Price: 59.90}
data, err := json.Marshal(p)
// {"id":"p-1","name":"Camiseta","price":59.9}

// Unmarshal — JSON → struct
var product Product
err = json.Unmarshal(data, &product)

// Encoder/Decoder — mais eficiente para streams (HTTP bodies)
json.NewEncoder(w).Encode(product)              // escreve em io.Writer
json.NewDecoder(r.Body).Decode(&product)        // lê de io.Reader

// json.RawMessage — adia o decode de parte do JSON
type Event struct {
    Type    string          `json:"type"`
    Payload json.RawMessage `json:"payload"`
}

// Custom marshaler — controle total sobre serialização
type Money struct {
    Amount   int    // centavos
    Currency string
}

func (m Money) MarshalJSON() ([]byte, error) {
    return json.Marshal(map[string]any{
        "amount":   fmt.Sprintf("%.2f", float64(m.Amount)/100),
        "currency": m.Currency,
    })
}

func (m *Money) UnmarshalJSON(data []byte) error {
    var raw struct {
        Amount   string `json:"amount"`
        Currency string `json:"currency"`
    }
    if err := json.Unmarshal(data, &raw); err != nil {
        return err
    }
    f, err := strconv.ParseFloat(raw.Amount, 64)
    if err != nil {
        return err
    }
    m.Amount = int(f * 100)
    m.Currency = raw.Currency
    return nil
}

Database com database/sql e sqlx: queries, scan, transações

database/sql é a interface padrão de banco de dados. sqlx adiciona StructScan, Get, Select e queries nomeadas sobre ela — sem ser um ORM completo.

import (
    "database/sql"
    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq" // driver PostgreSQL (blank import)
)

// Abrir conexão com pool
db, err := sqlx.Open("postgres", "postgres://dev:dev@localhost/mydb?sslmode=disable")
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)

// Query simples — sqlx.Get para um resultado
var order Order
err = db.GetContext(ctx, &order, "SELECT * FROM orders WHERE id=$1", id)
if errors.Is(err, sql.ErrNoRows) {
    return nil, ErrNotFound
}

// Query múltiplos — sqlx.Select
var orders []Order
err = db.SelectContext(ctx, &orders,
    "SELECT * FROM orders WHERE customer_id=$1 ORDER BY created_at DESC",
    customerID,
)

// Prepared statement — reutiliza o plano de query
stmt, err := db.PrepareContext(ctx, "INSERT INTO orders(id, total, status) VALUES($1, $2, $3)")
defer stmt.Close()
_, err = stmt.ExecContext(ctx, order.ID, order.Total, order.Status)

// Transação
func transferFunds(ctx context.Context, db *sqlx.DB, fromID, toID string, amount float64) error {
    tx, err := db.BeginTxx(ctx, nil)
    if err != nil {
        return fmt.Errorf("begin tx: %w", err)
    }
    defer tx.Rollback() // rollback se Commit não for chamado

    _, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - $1 WHERE id=$2", amount, fromID)
    if err != nil {
        return fmt.Errorf("debit: %w", err)
    }

    _, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + $1 WHERE id=$2", amount, toID)
    if err != nil {
        return fmt.Errorf("credit: %w", err)
    }

    return tx.Commit()
}

// Named query — parâmetros por nome (:campo)
query := `INSERT INTO orders(id, customer_id, total) VALUES(:id, :customer_id, :total)`
_, err = db.NamedExecContext(ctx, query, order)

// Scan manual com database/sql padrão
rows, err := db.QueryContext(ctx, "SELECT id, status, total FROM orders")
defer rows.Close()
for rows.Next() {
    var o Order
    if err := rows.Scan(&o.ID, &o.Status, &o.Total); err != nil {
        return nil, err
    }
    orders = append(orders, o)
}
return orders, rows.Err()

Versões — O que mudou

VersãoAnoPrincipais novidades
1.182022Generics (type parameters, constraints) — maior feature da história do Go; fuzzing nativo; workspace mode (go.work) para monorepos
1.202023errors.Join para combinar múltiplos erros; http.ResponseController; conversão de slice para array; melhorias de performance no GC
1.212023Built-ins min, max, clear; pacotes slices, maps e cmp na stdlib; log/slog (structured logging nativo); suporte a WASI
1.222024for i := range N (range sobre inteiro); cada iteração de loop cria sua própria variável (corrige a armadilha clássica de closure); net/http.ServeMux com método HTTP e wildcards (GET /orders/{id})
1.232024Range over functions — iteradores customizados com func(yield func(K,V) bool); melhorias em Timer/Ticker; package unique; iter package na stdlib