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)) // 100Controle 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} // recomendadoInterfaces: 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ó recebeConcorrê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, ¬Found) {
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 monorepoTesting: 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ão | Ano | Principais novidades |
|---|---|---|
| 1.18 | 2022 | Generics (type parameters, constraints) — maior feature da história do Go; fuzzing nativo; workspace mode (go.work) para monorepos |
| 1.20 | 2023 | errors.Join para combinar múltiplos erros; http.ResponseController; conversão de slice para array; melhorias de performance no GC |
| 1.21 | 2023 | Built-ins min, max, clear; pacotes slices, maps e cmp na stdlib; log/slog (structured logging nativo); suporte a WASI |
| 1.22 | 2024 | for 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.23 | 2024 | Range over functions — iteradores customizados com func(yield func(K,V) bool); melhorias em Timer/Ticker; package unique; iter package na stdlib |