Skip to content

Go Folha de referência

Linguagem rápida, estaticamente tipada, construída para simplicidade e concorrência.

01

Primeiros Passos

Hello World

Todo programa Go começa na função main() do package main. Use 'go run' para executar, 'go build' para compilar. gofmt formata código automaticamente (tabs, espaçamento). O statement import traz pacotes — fmt lida com I/O formatado.

go
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
    fmt.Printf("Name: %s, Age: %d\n", "Alice", 30)
}

// Run: go run main.go
// Build: go build -o app main.go
// Format: gofmt -w main.go

Go Modules (go.mod)

Go modules (go.mod) gerenciam dependências desde Go 1.11. 'go mod init' cria o arquivo de módulo. 'go get' adiciona dependências. 'go mod tidy' remove imports não utilizados e adiciona os ausentes. O caminho do módulo é o import path para seu pacote.

go
// Initialize a new module
// $ go mod init github.com/user/project

// go.mod file:
module github.com/user/project

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    golang.org/x/sync v0.3.0
)

// Add dependencies:
// $ go get github.com/gin-gonic/gin
// $ go mod tidy  // clean up unused deps

Estrutura de Pacotes

Go organiza código em pacotes — um pacote por diretório. Nomes capitalizados (Println, Add) são exportados (públicos); nomes lowercase (privados) são package-private. O diretório 'internal' restringe imports ao módulo parent. O nome do pacote deve corresponder ao nome do diretório.

go
// project structure:
// myproject/
//   go.mod
//   main.go          // package main
//   utils/
//     helper.go      // package utils
//     math.go
//   internal/
//     api/           // private (only importable within parent)

// main.go
package main

import (
    "fmt"
    "github.com/user/project/utils"
)

func main() {
    fmt.Println(utils.Add(1, 2))
}

// Exported names start with uppercase letter
// unexported names start with lowercase letter

Variáveis & Constantes

Use 'var name type = value' para declarações explícitas, 'name := value' para declarações curtas (apenas funções). Go tem zero values: 0 para números, '' para strings, false para booleanos, nil para pointers/slices/maps. Constantes (const) são em tempo de compilação e não podem usar :=.

go
package main

import "fmt"

// Package-level declarations
var version = "1.0.0" // inferred type
const Pi = 3.14159

func main() {
    // var with type
    var name string = "Alice"
    var age int = 30

    // Short declaration (only inside functions)
    city := "NYC"

    // Multiple assignment
    a, b, c := 1, 2, 3

    // Zero values (default)
    var x int      // 0
    var s string   // ""
    var ok bool    // false
    var p *int     // nil

    fmt.Println(name, age, city, a, b, c, x, s, ok, p)
}

Tipos Básicos & Conversão

Go requer conversão de tipo explícita — não há conversão implícita como em C. rune é um alias para int32 (representa um code point Unicode). byte é um alias para uint8. Converter entre tipos numéricos pode perder precisão (float para int trunca). string(65) converte um code point para seu caractere.

go
// Numeric types
var i int = 42
var i8 int8 = 127
var u uint = 42
var f32 float32 = 3.14
var f64 float64 = 3.141592653589793

// Other types
var b bool = true
var s string = "hello"
var r rune = 'A' // int32 alias for Unicode code point
var by byte = 255 // uint8 alias

// Type conversion (explicit, no implicit conversion)
var n int = 42
var f float64 = float64(n)  // int -> float64
var s string = string(65)   // 65 -> "A" (rune to string)
var i2 int = int(f)         // float64 -> int (truncates)

// Type inference
var x = 42        // int
var y = 3.14      // float64
var z = len("hi") // int
02

Strings & Formatação

Básico de Strings

Strings em Go são sequências de bytes imutáveis. len() retorna a contagem de bytes, não de caracteres — use utf8.RuneCountInString() para Unicode. Strings são comparadas lexicograficamente com ==, <, >. Range sobre uma string itera por rune (code point Unicode), lidando com caracteres multi-byte corretamente.

go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, World"

    // Length (bytes, not runes!)
    fmt.Println(len(s)) // 12

    // Concatenation
    s1 := "Hello" + " " + "World"
    s2 := fmt.Sprintf("%s = %d", "age", 30)

    // Comparison
    fmt.Println("abc" == "abc") // true
    fmt.Println("a" < "b")      // true (lexicographic)

    // Iteration (by rune)
    for i, r := range "Go语" {
        fmt.Printf("%d: %c\n", i, r)
    }
}

Pacote strings

O pacote strings fornece operações comuns de string. Contains/HasPrefix/HasSuffix verificam substrings. Split quebra por delimitador; Join combina. Replace recebe uma contagem (-1 para todas). TrimSpace remove espaços em branco iniciais/finais. Todas as funções retornam novas strings (strings são imutáveis).

go
import (
    "fmt"
    "strings"
)

s := "Hello, World"

// Case
strings.ToUpper(s)         // "HELLO, WORLD"
strings.ToLower(s)         // "hello, world"
strings.Title("hello world") // "Hello World"

// Search
strings.Contains(s, "World")  // true
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "World") // true
strings.Index(s, "World")     // 7 (-1 if not found)
strings.Count(s, "l")         // 3

// Split & Join
parts := strings.Split("a,b,c", ",")  // ["a", "b", "c"]
joined := strings.Join(parts, "-")    // "a-b-c"

// Replace
strings.Replace(s, "l", "L", 1)  // "HeLlo, World" (1 occurrence)
strings.ReplaceAll(s, "l", "L")  // "HeLLo, WorLd"
strings.TrimSpace("  hi  ")      // "hi"

strconv & Formatação

strconv converte entre strings e números — sempre verifique o retorno de erro. fmt.Printf formata saída: %d (int), %f (float), %s (string), %t (bool), %T (tipo), %x (hex), %q (quoted). Use %.2f para 2 casas decimais, %05d para zero-padding. Sprintf retorna a string em vez de imprimir.

go
import (
    "fmt"
    "strconv"
)

// String to number
n, err := strconv.Atoi("42")     // string -> int
f, err := strconv.ParseFloat("3.14", 64)

// Number to string
s1 := strconv.Itoa(42)            // int -> string
s2 := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"

// Printf formatting
fmt.Printf("Int: %d\n", 42)
fmt.Printf("Float: %.2f\n", 3.14159)
fmt.Printf("String: %s\n", "hi")
fmt.Printf("Bool: %t\n", true)
fmt.Printf("Type: %T\n", 42)     // int
fmt.Printf("Hex: %x\n", 255)     // ff
fmt.Printf("Pad: %05d\n", 42)    // 00042
fmt.Printf("Quote: %q\n", "hi")  // "hi"

// Sprintf returns formatted string
result := fmt.Sprintf("Name: %s, Age: %d", "Alice", 30)

Raw Strings & Multilinha

Raw strings (backticks) preservam tudo literalmente — sem sequências de escape, podem abranger múltiplas linhas. Use para regex, SQL, templates HTML. Interpreted strings (aspas duplas) processam \n, \t, etc. Para concatenação eficiente de strings em loops, use strings.Builder (evita alocação O(n²)).

go
// Raw string literal (backticks) - no escape processing
raw := `This is a
multiline string
with \n (literal backslash-n)`

// Interpreted string (double quotes) - processes escapes
interpreted := "Line1\nLine2\tTabbed"

// Raw strings are useful for:
// - Regex patterns
// - HTML/SQL templates
// - File paths on Windows
regex := `^d{4}-d{2}-d{2}$`

// String builder (efficient concatenation)
var sb strings.Builder
for i := 0; i < 1000; i++ {
    sb.WriteString("line\n")
}
result := sb.String()

Unicode & Runes

Strings em Go são sequências de bytes codificadas em UTF-8. len() dá bytes; utf8.RuneCountInString() dá caracteres. Range sobre uma string decodifica UTF-8 automaticamente. Para modificar uma string, converta para []rune, altere e converta de volta. Isso é essencial para processamento de texto internacionalizado.

go
import (
    "fmt"
    "unicode/utf8"
)

s := "Hello, 世界"

// Byte length vs rune length
fmt.Println(len(s))                    // 13 (bytes)
fmt.Println(utf8.RuneCountInString(s)) // 9 (runes/characters)

// Decode runes manually
for i := 0; i < len(s); {
    r, size := utf8.DecodeRuneInString(s[i:])
    fmt.Printf("%c ", r)
    i += size
}

// Range automatically decodes runes
for i, r := range s {
    fmt.Printf("%d:%c ", i, r)
}

// Rune slice (mutable)
runes := []rune("Hello")
runes[0] = 'J'
s2 := string(runes) // "Jello"
03

Fluxo de Controle

If / Else

O if do Go não precisa de parênteses ao redor de condições, mas chaves são obrigatórias (mesmo para corpos de uma linha). O init statement (if x := f(); x > 0) é escopado ao bloco if/else — comum para verificação de erros. Esse padrão mantém o escopo de variáveis restrito.

go
// Basic if/else
score := 85
if score >= 90 {
    fmt.Println("A")
} else if score >= 80 {
    fmt.Println("B")
} else {
    fmt.Println("C")
}

// If with initialization statement
if n := computeValue(); n > 100 {
    fmt.Println("big:", n)
} else {
    fmt.Println("small:", n)
}
// n is not accessible here (scoped to if)

// No parentheses needed, but braces are required

Loops For

Go tem apenas uma palavra-chave de loop: 'for'. Ela lida com estilo C, estilo while (for cond), infinito (for) e iteração (for range). Range funciona com slices, maps, strings e channels. Use _ para pular o índice ou valor. A ordem de iteração de map é aleatória por design.

go
// C-style for
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// While-style (condition only)
n := 10
for n > 0 {
    n--
}

// Infinite loop
for {
    break
}

// Range (iterate slices, maps, strings)
nums := []int{10, 20, 30}
for index, value := range nums {
    fmt.Printf("%d: %d\n", index, value)
}

// Skip index or value with _
for _, value := range nums {
    fmt.Println(value)
}

// Range over map (unordered!)
m := map[string]int{"a": 1, "b": 2}
for key, val := range m {
    fmt.Println(key, val)
}

Switch

O switch do Go não faz fall through por padrão (diferente de C/Java) — cada case é um branch separado. Use fallthrough para forçar. Múltiplos valores por case usam vírgulas. Switch sem expressão age como uma cadeia if/else mais limpa. Switch com init statement escopa a variável ao switch.

go
// Basic switch
day := 3
switch day {
case 1:
    fmt.Println("Mon")
case 2, 3, 4, 5:
    fmt.Println("Weekday")
case 6, 7:
    fmt.Println("Weekend")
default:
    fmt.Println("Invalid")
}

// Switch with no expression (like if/else chain)
switch {
case score >= 90:
    grade = "A"
case score >= 80:
    grade = "B"
default:
    grade = "C"
}

// Switch with init
switch os := runtime.GOOS; os {
case "linux":
    fmt.Println("Linux")
case "darwin":
    fmt.Println("macOS")
}

// Fallthrough (rare, goes to next case unconditionally)
switch 1 {
case 1:
    fmt.Println("one")
    fallthrough
case 2:
    fmt.Println("two") // executes even though value is 1
}

Defer

defer agenda uma chamada de função para executar quando a função envolvente retorna — last-in-first-out (LIFO). Use para limpeza (fechar arquivos, liberar locks, fechar conexões). Argumentos de chamadas deferred são avaliados imediatamente, mas a chamada executa no return. Defers executam mesmo se a função entrar em panic.

go
// Defer runs when function returns (LIFO order)
func main() {
    defer fmt.Println("third")
    defer fmt.Println("second")
    fmt.Println("first")
}
// Output: first, second, third

// Common: resource cleanup
func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // runs when readFile returns

    // ... use f ...
    return nil
}

// Defer evaluates arguments immediately
i := 1
defer fmt.Println(i) // prints 1 (not 2)
i = 2

Goto, Break, Continue

break sai do loop mais interno; continue pula para a próxima iteração. Labels (break outer) saem de loops aninhados. goto existe mas raramente é usado — prefira fluxo de controle estruturado. Labels também podem ser usados com continue para pular para a próxima iteração de um loop externo.

go
// Break and continue
for i := 0; i < 10; i++ {
    if i%2 == 0 {
        continue // skip even numbers
    }
    if i > 7 {
        break // exit loop
    }
    fmt.Println(i)
}

// Break with label (for nested loops)
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break outer // exits both loops
        }
    }
}

// Goto (rarely used, avoid)
i := 0
loop:
    if i < 5 {
        fmt.Println(i)
        i++
        goto loop
    }

Select (Operações de Channel)

select é como switch para channels — ele espera em múltiplas operações de channel e escolhe a primeira pronta. O case default o torna non-blocking. Use select em um loop para padrões event-driven. time.After() cria um channel de timeout. select escolhe aleatoriamente se múltiplos cases estão prontos, prevenindo starvation.

go
// Select waits on multiple channel operations
select {
case msg := <-messages:
    fmt.Println("received:", msg)
case <-timeout:
    fmt.Println("timed out")
default:
    fmt.Println("no activity") // non-blocking
}

// Select with send
ch1 := make(chan int)
ch2 := make(chan int)
go func() { ch1 <- 1 }()
go func() { ch2 <- 2 }()

select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
}

// Select in a loop (event loop pattern)
for {
    select {
    case msg := <-ch:
        fmt.Println(msg)
    case <-time.After(5 * time.Second):
        return // timeout after 5s of inactivity
    }
}
04

Funções

Definir & Múltiplos Retornos

Funções em Go podem retornar múltiplos valores — o padrão canônico é (result, error). Named returns agem como variáveis declaradas inicializadas com zero values; um return 'nu' as usa. Named returns melhoram legibilidade para funções complexas, mas podem ser confusos se usados em excesso. Sempre verifique erros imediatamente.

go
// Function with multiple return values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divide by zero")
    }
    return a / b, nil
}

// Named return values
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // "naked" return (uses named values)
}

// Usage
result, err := divide(10, 2)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result) // 5

x, y := split(100)
fmt.Println(x, y)

Variadic & Closures

Funções variadic (...T) aceitam qualquer número de argumentos, recebidos como um slice. Espalhe um slice com .... Closures capturam variáveis por referência de seu escopo envolvente — a função counter() retorna uma closure que lembra 'count'. Closures são úteis para callbacks, iteradores e funções stateful.

go
// Variadic function (variable arguments)
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}
fmt.Println(sum(1, 2, 3))       // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15

// Spread a slice
nums := []int{1, 2, 3}
fmt.Println(sum(nums...)) // 6

// Closure (function that captures variables)
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

next := counter()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3

Funções como Valores

Funções em Go são valores first-class — podem ser atribuídas a variáveis, passadas como argumentos e armazenadas em estruturas de dados. Defina tipos de função com 'type Name func(params) returns'. Maps de funções são úteis para dispatch tables, command handlers e strategy patterns.

go
// Functions are first-class values
func apply(f func(int) int, x int) int {
    return f(x)
}

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

fmt.Println(apply(double, 5)) // 10
fmt.Println(apply(square, 5)) // 25

// Function type
type MathFunc func(int) int

var fn MathFunc = func(x int) int { return x + 1 }
fmt.Println(fn(10)) // 11

// Map of functions
ops := map[string]func(int, int) int{
    "add": func(a, b int) int { return a + b },
    "sub": func(a, b int) int { return a - b },
    "mul": func(a, b int) int { return a * b },
}
fmt.Println(ops["add"](3, 4)) // 7

init() & Funções Anônimas

Funções init() executam automaticamente antes de main(), na ordem em que são declaradas. Use para setup (carregamento de config, validação, registro). Múltiplas funções init() por arquivo são permitidas. Funções anônimas podem ser imediatamente invocadas (IIFE) ou atribuídas a variáveis. Elas também são usadas para goroutines.

go
// init() runs before main(), once per package
// Multiple init() functions allowed, run in order declared
package main

import "fmt"

var config string

func init() {
    // Setup code: config, connections, validation
    config = "production"
    fmt.Println("init 1")
}

func init() {
    fmt.Println("init 2, config:", config)
}

func main() {
    fmt.Println("main")
}
// Output: init 1, init 2, main

// Anonymous function (IIFE)
result := func(x int) int {
    return x * 2
}(5) // immediately invoked, result = 10

// Goroutine with anonymous function
go func(msg string) {
    fmt.Println(msg)
}("async")

Generics (Go 1.18+)

Go 1.18+ suporta generics com type parameters [T any]. Type constraints (interfaces) restringem tipos permitidos — use 'any' para sem restrição, 'comparable' para operadores == / !=. Union constraints (int | float64) permitem tipos específicos. Generics habilitam estruturas de dados e algoritmos type-safe reutilizáveis sem duplicação de código.

go
// Generic function with type parameter
func Map[T, U any](slice []T, f func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = f(v)
    }
    return result
}

nums := []int{1, 2, 3}
doubled := Map(nums, func(n int) int { return n * 2 })
// [2, 4, 6]

strs := Map(nums, func(n int) string {
    return fmt.Sprintf("num%d", n)
})
// ["num1", "num2", "num3"]

// Type constraints
type Number interface {
    int | int64 | float64
}

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

// comparable constraint (for maps/slices keys)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

Métodos

Métodos são funções com um argumento receiver. Value receivers (r Rectangle) trabalham em uma cópia — não podem modificar o original. Pointer receivers (r *Rectangle) podem modificar e evitam copiar structs grandes. Consistência importa: se um método usa pointer receiver, todos deveriam. Métodos podem ser definidos em qualquer tipo no mesmo pacote.

go
type Rectangle struct {
    Width, Height float64
}

// Method with value receiver
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Method with pointer receiver (can modify)
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// Method on non-struct types
type MyString string

func (s MyString) Shout() string {
    return strings.ToUpper(string(s)) + "!"
}

// Usage
r := Rectangle{Width: 10, Height: 5}
fmt.Println(r.Area()) // 50
r.Scale(2)
fmt.Println(r.Width)  // 20

s := MyString("hello")
fmt.Println(s.Shout()) // HELLO!
05

Estruturas de Dados

Arrays & Slices

Arrays têm comprimento fixo; slices são dinâmicos (backed por arrays). append() adiciona elementos, crescendo a capacidade conforme necessário. make([]T, len, cap) pré-aloca para eficiência. Slicing cria uma view (compartilha o array subjacente) — use copy() para dados independentes. Sempre verifique len/cap ao otimizar.

go
// Array (fixed length, rarely used directly)
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{4, 5, 6} // size inferred

// Slice (dynamic array, most common)
nums := []int{1, 2, 3}
nums = append(nums, 4)        // [1, 2, 3, 4]
nums = append(nums, 5, 6, 7) // [1, 2, 3, 4, 5, 6, 7]

// Make a slice with capacity
s := make([]int, 3, 10) // len=3, cap=10

// Slicing
sub := nums[1:4]  // [2, 3, 4]
first := nums[:2] // [1, 2]
last := nums[3:]  // [4, 5, 6, 7]

// Copy
dst := make([]int, len(nums))
copy(dst, nums)

// Length and capacity
fmt.Println(len(nums), cap(nums))

Maps

Maps são as hash tables do Go — pares chave/valor não ordenados. O padrão comma-ok (val, ok := m[key]) verifica se uma chave existe. delete() remove uma chave. A ordem de iteração de map é aleatória por design. Maps são tipos de referência — passá-los a funções compartilha os dados subjacentes. nil maps não podem ser escritos (use make()).

go
// Create a map
m := map[string]int{
    "Alice": 30,
    "Bob":   25,
}

// Using make
ages := make(map[string]int)
ages["Charlie"] = 35

// Access
fmt.Println(m["Alice"]) // 30

// Check existence (comma-ok pattern)
age, ok := m["David"]
if !ok {
    fmt.Println("not found")
}

// Delete
delete(m, "Bob")

// Iterate (random order)
for name, age := range m {
    fmt.Printf("%s: %d\n", name, age)
}

// Length
fmt.Println(len(m))

// Nested maps
matrix := map[string]map[string]int{}
matrix["row1"] = map[string]int{"col1": 1}

Structs

Structs agrupam campos relacionados. Use inicialização nomeada (Person{Name: ...}) para clareza. new() retorna um pointer com zero values. Structs anônimos são úteis para formas de dados one-off. Struct embedding (sem nome de campo) promove os campos e métodos do struct embedded — a alternativa do Go à herança.

go
// Define a struct
type Person struct {
    Name string
    Age  int
    Address string // field names are exported (capitalized)
}

// Create instances
p1 := Person{Name: "Alice", Age: 30}
p2 := Person{"Bob", 25, "NYC"} // positional (not recommended)
p3 := new(Person)              // returns *Person, zero values
p3.Name = "Charlie"

// Anonymous struct (one-off)
config := struct {
    Port int
    Host string
}{
    Port: 8080,
    Host: "localhost",
}

// Struct embedding (composition)
type Employee struct {
    Person          // embedded (promotes fields)
    Salary   float64
}

emp := Employee{
    Person: Person{Name: "Dave", Age: 40},
    Salary: 50000,
}
fmt.Println(emp.Name) // "Dave" (promoted from Person)

Pointers

Pointers em Go (*T) mantêm endereços de memória. & pega o endereço, * dereferencia. Diferente de C, Go não tem aritmética de pointers (mais seguro). Struct pointers permitem shorthand (u.Name em vez de (*u).Name). nil pointers causam panic ao dereferenciar. Go tem garbage collection — sem free() manual necessário.

go
// Pointer basics
x := 42
p := &x          // p is *int, points to x
fmt.Println(*p)  // 42 (dereference)
*p = 100         // modify x through pointer
fmt.Println(x)   // 100

// new() allocates and returns pointer
p2 := new(int)   // *int, value 0
*p2 = 42

// Pointers to structs
type User struct{ Name string }
u := &User{Name: "Alice"} // *User
u.Name = "Bob"            // (*u).Name shorthand
fmt.Println(u.Name)       // Bob

// nil pointer
var p3 *int // nil
// *p3 // panic: nil pointer dereference

// No pointer arithmetic (unlike C)
// p++ is illegal

Operações com Slice

Go não tem filter/map/reduce integrados — escreva-os ou use o pacote slices (Go 1.21+). Insert/remove requerem shifting de elementos com append+copy. Cuidado com slice aliasing: s[:i] e s[i+1:] compartilham o array subjacente. Para slices 2D, aloque cada linha separadamente.

go
// Filter
func filter(nums []int, pred func(int) bool) []int {
    result := []int{}
    for _, n := range nums {
        if pred(n) {
            result = append(result, n)
        }
    }
    return result
}

evens := filter([]int{1,2,3,4,5}, func(n int) bool {
    return n%2 == 0
}) // [2, 4]

// Insert at index
func insert(s []int, i, v int) []int {
    s = append(s, 0)
    copy(s[i+1:], s[i:])
    s[i] = v
    return s
}

// Remove at index
func remove(s []int, i int) []int {
    return append(s[:i], s[i+1:]...)
}

// 2D slice
matrix := make([][]int, 3)
for i := range matrix {
    matrix[i] = make([]int, 4)
}

Ordenação & Busca

sort.Ints/Strings/Float64s ordenam in place. sort.Slice com um comparator lida com tipos personalizados. sort.Search* faz binary search em slices ordenados. Go 1.21+ adiciona o pacote slices com funções de ordenação genéricas. Toda ordenação é in-place — faça uma cópia primeiro se precisar da ordem original.

go
import "sort"

// Sort a slice
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
sort.Ints(nums) // [1 1 2 3 4 5 6 9]
fmt.Println(nums)

// Sort strings
strs := []string{"banana", "apple", "cherry"}
sort.Strings(strs) // [apple banana cherry]

// Sort with custom comparator
type Person struct {
    Name string
    Age  int
}

people := []Person{
    {"Alice", 30},
    {"Bob", 25},
    {"Charlie", 35},
}

sort.Slice(people, func(i, j int) bool {
    return people[i].Age < people[j].Age // by age ascending
})

// Binary search (sorted slice)
idx := sort.SearchInts(nums, 4) // index of 4

// Sort package (Go 1.21+): slices.Sort
import "slices"
slices.Sort(people) // needs Less method or cmp func
06

Métodos & Interfaces

Definindo Interfaces

Interfaces definem assinaturas de métodos. Go usa tipagem estrutural — um tipo implementa uma interface automaticamente se tem todos os métodos necessários (sem declaração 'implements' explícita). Isso habilita design desacoplado: defina interfaces onde você as usa, não onde as implementa.

go
// Interface: a set of method signatures
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Implement implicitly (no 'implements' keyword)
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Use the interface
func describe(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n",
        s.Area(), s.Perimeter())
}

c := Circle{Radius: 5}
describe(c) // works! Circle implements Shape

Interface Vazia & Type Assertions

interface{} (ou 'any' no Go 1.18+) mantém qualquer valor. Type assertion (v.(T)) extrai o tipo concreto — panic se tipo errado, então use o padrão comma-ok. Type switch (switch v.(type)) lida com múltiplos tipos de forma limpa. Interface vazia é útil para contêineres genéricos, mas perde type safety — prefira generics.

go
// Empty interface (any type)
// Go 1.18+: 'any' is an alias for interface{}
func printAny(v any) {
    fmt.Println(v)
}

printAny(42)
printAny("hello")
printAny([]int{1, 2, 3})

// Type assertion
var i any = "hello"
s := i.(string)    // panics if not string
fmt.Println(s)

// Safe type assertion (comma-ok)
s, ok := i.(string)
if ok {
    fmt.Println("string:", s)
}

// Type switch
func describe(v any) {
    switch x := v.(type) {
    case int:
        fmt.Printf("int: %d\n", x)
    case string:
        fmt.Printf("string: %s\n", x)
    case []int:
        fmt.Printf("int slice: %v\n", x)
    default:
        fmt.Printf("unknown type: %T\n", x)
    }
}

Composição de Interfaces

Interfaces podem embedar outras interfaces (composição). io.Reader e io.Writer são as interfaces mais importantes do Go — implementadas por arquivos, conexões de rede, buffers, etc. Isso habilita abstrações poderosas: funções que recebem io.Reader funcionam com qualquer fonte legível. Interfaces pequenas e focadas (1-3 métodos) são idiomáticas.

go
// Compose interfaces
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter combines Reader and Writer
type ReadWriter interface {
    Reader
    Writer
}

// io.Reader and io.Writer are built-in interfaces
// Many types implement them: *os.File, *bytes.Buffer, net.Conn

func copyData(r io.Reader, w io.Writer) error {
    buf := make([]byte, 1024)
    for {
        n, err := r.Read(buf)
        if n > 0 {
            if _, err := w.Write(buf[:n]); err != nil {
                return err
            }
        }
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
    }
    return nil
}

Pointer vs Value Receivers

Pointer receivers podem modificar o struct e evitam copiar valores grandes. Value receivers são seguros (não podem modificar) e permitem o método em tanto valores quanto pointers. Se algum método tem pointer receiver, todos deveriam (para consistência). Métodos pointer receiver satisfazem interfaces para tanto T quanto *T; métodos value receiver apenas para T.

go
type Counter struct {
    count int
}

// Value receiver: works on a copy, can't modify
func (c Counter) Get() int {
    return c.count
}

// Pointer receiver: can modify the original
func (c *Counter) Increment() {
    c.count++
}

c := Counter{}
c.Increment() // Go auto-takes address (&c).Increment()
c.Increment()
fmt.Println(c.Get()) // 2

// Interface implementation note:
// *Counter implements both Get() and Increment()
// Counter (value) implements only Get()
// So *Counter satisfies an interface requiring both

Interface Stringer

A interface Stringer (String() string) controla como um tipo aparece em Print/Printf — como toString() em Java/JS. %v usa String(); %+v mostra nomes de campos; %#v mostra sintaxe Go. A interface error funciona da mesma forma: implemente Error() string para tornar qualquer tipo um error. Essas são as interfaces integradas mais comuns do Go.

go
// Stringer interface (like toString() in other languages)
type Stringer interface {
    String() string
}

type Person struct {
    Name string
    Age  int
}

// Implement Stringer
func (p Person) String() string {
    return fmt.Sprintf("%s (%d years)", p.Name, p.Age)
}

p := Person{Name: "Alice", Age: 30}
fmt.Println(p)       // Alice (30 years) — uses String()
fmt.Printf("%v\n", p) // Alice (30 years)
fmt.Printf("%+v\n", p) // Name:Alice Age:30
fmt.Printf("%#v\n", p) // main.Person{Name:"Alice", Age:30}

// Error interface is similar:
// type error interface { Error() string }
// Implement Error() to make a type usable as an error

Type Embedding & Composição

Go usa composição em vez de herança. Embeddar um struct promove seus campos e métodos ao struct externo. Você pode sobrescrever métodos promovidos definindo um método com o mesmo nome no tipo externo. Embeddar uma interface permite decorating/delegating — o tipo externo satisfaz a interface e pode encaminhar chamadas.

go
// Embed a struct (composition over inheritance)
type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return a.Name + " makes a sound"
}

type Dog struct {
    Animal // embedded — promotes Name and Speak()
    Breed  string
}

d := Dog{
    Animal: Animal{Name: "Rex"},
    Breed:  "Labrador",
}

fmt.Println(d.Name)      // "Rex" (promoted)
fmt.Println(d.Speak())   // "Rex makes a sound" (promoted)
fmt.Println(d.Animal.Name) // explicit access also works

// Override a promoted method
func (d Dog) Speak() string {
    return d.Name + " barks!"
}
fmt.Println(d.Speak()) // "Rex barks!"

// Embed an interface
type Logger struct {
    io.Writer // embed interface
}
// Logger now has Write() method, delegating to the embedded Writer
07

Tratamento de Erros

Básico de Erros

Go trata erros como valores, não exceções. Funções retornam (result, error) — sempre verifique err != nil imediatamente. errors.New() cria erros simples; fmt.Errorf() adiciona formatação. Sentinel errors (var ErrX = errors.New()) habilitam comparação com ==. Nunca ignore erros (use _ apenas quando intencional).

go
// Functions return errors as last return value
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return a / b, nil
}

// Always check errors immediately
result, err := divide(10, 0)
if err != nil {
    log.Fatal(err) // or handle gracefully
}
fmt.Println(result)

// fmt.Errorf for formatted errors
func validate(age int) error {
    if age < 0 {
        return fmt.Errorf("invalid age: %d (must be positive)", age)
    }
    return nil
}

// Sentinel errors
var ErrNotFound = errors.New("not found")
if err := lookup("key"); err == ErrNotFound {
    // handle not found
}

Tipos de Erro Personalizados

Tipos de erro personalizados implementam a interface error (Error() string). Eles carregam dados estruturados (campos, códigos, contexto) além de uma mensagem simples. Use type assertion (*ValidationError) para acessar os dados. Isso é essencial para tratamento de erros específico de domínio — ex.: códigos de status HTTP, detalhes de validação, lógica de retry.

go
// Custom error type (implements error interface)
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on '%s': %s", e.Field, e.Message)
}

// Use the custom error
func validateUser(name string) error {
    if name == "" {
        return &ValidationError{
            Field:   "name",
            Message: "cannot be empty",
        }
    }
    return nil
}

// Type-assert to access fields
err := validateUser("")
if ve, ok := err.(*ValidationError); ok {
    fmt.Println("Field:", ve.Field)   // "name"
    fmt.Println("Message:", ve.Message) // "cannot be empty"
}

errors.Is & errors.As (Go 1.13+)

Go 1.13+ adicionou error wrapping com %w (fmt.Errorf). errors.Is() verifica se um erro corresponde a um sentinel (desembrulhando a cadeia). errors.As() extrai um tipo de erro específico da cadeia. Isso habilita tratamento de erros em camadas: erros de baixo nível wrapped com contexto, verificados em níveis altos. Sempre wrap com %w (não %v) para preservar a cadeia.

go
import "errors"

// Wrapped errors with %w
var ErrNotFound = errors.New("not found")

func getUser(id int) error {
    if id == 0 {
        return fmt.Errorf("getUser(%d): %w", id, ErrNotFound)
    }
    return nil
}

// errors.Is: check if error matches (unwraps the chain)
err := getUser(0)
if errors.Is(err, ErrNotFound) {
    fmt.Println("user not found")
}

// errors.As: extract a specific error type
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("field:", ve.Field)
}

// errors.Unwrap: get the wrapped error
inner := errors.Unwrap(err)

Panic & Recover

panic() é para erros irrecuperáveis (bugs, violações de invariantes) — não para tratamento de erros normal. recover() captura panics, mas só funciona em funções deferred. Use panic/recover para: erros de programação (índice fora do intervalo), falhas de inicialização de pacote e proteger goroutines de travar o programa. Prefira retornar erros para falhas esperadas.

go
// Panic: unrecoverable error (like throw)
func mustParse(s string) int {
    n, err := strconv.Atoi(s)
    if err != nil {
        panic(fmt.Sprintf("invalid number: %s", s))
    }
    return n
}

// Recover: catch a panic (only in deferred functions)
func safeCall() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recovered:", r)
        }
    }()
    panic("something went wrong")
}

// Practical: safe goroutine
func safeGo(fn func()) {
    go func() {
        defer func() {
            if r := recover(); r != nil {
                log.Println("goroutine panic:", r)
            }
        }()
        fn()
    }()
}

Padrões de Error Wrapping

Envolva erros com contexto em cada camada usando fmt.Errorf com %w. Isso cria uma cadeia de erros: o nível top-level vê o caminho completo (getUserProfile → fetchUser → sql error). O contexto (nome da função, parâmetros) ajuda na depuração. Use errors.Is/As para verificar erros específicos na cadeia. Evite envolver o mesmo erro múltiplas vezes com o mesmo contexto.

go
// Wrap errors with context as they propagate up
func fetchUser(id int) (*User, error) {
    row := db.QueryRow("SELECT ... WHERE id = ?", id)
    var u User
    if err := row.Scan(&u.Name, &u.Age); err != nil {
        return nil, fmt.Errorf("fetchUser(%d): %w", id, err)
    }
    return &u, nil
}

func getUserProfile(id int) (*Profile, error) {
    user, err := fetchUser(id)
    if err != nil {
        return nil, fmt.Errorf("getUserProfile(%d): %w", id, err)
    }
    // ...
}

// At the top level, log the full chain
profile, err := getUserProfile(42)
if err != nil {
    log.Printf("error: %v", err)
    // Output: getUserProfile(42): fetchUser(42): sql: no rows
}

// golang.org/x/xerrors or pkg/errors for stack traces

Defer, Panic, Recover Juntos

Combine defer (limpeza), panic (erros fatais) e recover (capturar panics) para gerenciamento robusto de recursos. Defers executam em ordem LIFO, mesmo durante panics. O named return value (err error) pode ser definido dentro de um recover deferred. Esse padrão garante que arquivos/conexões sejam fechados e transações revertidas, mesmo se o código entrar em panic.

go
// Complete pattern: cleanup + panic recovery
func processFile(path string) (err error) {
    file, e := os.Open(path)
    if e != nil {
        return e
    }
    defer func() {
        // Close file regardless of panic
        file.Close()

        // Recover from panic and convert to error
        if r := recover(); r != nil {
            err = fmt.Errorf("panic in processFile: %v", r)
        }
    }()

    // Do work that might panic
    data := parseFile(file) // might panic
    return saveData(data)
}

// Defer for resource cleanup (always runs)
func withDatabase(db *sql.DB, fn func(*sql.DB) error) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        if err != nil {
            tx.Rollback()
        } else {
            tx.Commit()
        }
    }()
    return fn(tx)
}
08

Concorrência

Goroutines

Goroutines são as threads leves do Go — inicie com 'go'. Elas são baratas (~2KB stack) e gerenciadas pelo runtime scheduler do Go (M:N scheduling). A função main não espera por goroutines — use sync.WaitGroup ou channels para sincronização. Nunca use time.Sleep para sincronização em produção (use WaitGroup).

go
package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Println(name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Launch goroutine with 'go' keyword
    go sayHello("Alice")
    go sayHello("Bob")

    // Anonymous goroutine
    go func() {
        fmt.Println("anonymous goroutine")
    }()

    // Wait for goroutines (simple approach)
    time.Sleep(1 * time.Second)
    fmt.Println("done")
}

// Goroutines are lightweight (~2KB stack, grows as needed)
// Millions of goroutines can run concurrently

Channels

Channels são conduits tipados para comunicação entre goroutines. Unbuffered (make(chan T)) bloqueia até tanto sender quanto receiver estiverem prontos (síncrono). Buffered (make(chan T, n)) bloqueia apenas quando cheio (assíncrono). O sender deve fechar channels, nunca o receiver. Range sobre um channel até que seja fechado. Channels habilitam 'share memory by communicating'.

go
// Unbuffered channel (synchronous)
ch := make(chan string)

// Send and receive
go func() {
    ch <- "hello" // blocks until received
}()
msg := <-ch // blocks until sent
fmt.Println(msg)

// Buffered channel (asynchronous)
buf := make(chan int, 3)
buf <- 1 // doesn't block (buffer has space)
buf <- 2
buf <- 3
// buf <- 4 // would block (buffer full)
fmt.Println(<-buf) // 1

// Close a channel (sender closes, never receiver)
close(buf)

// Range over channel (until closed)
for v := range buf {
    fmt.Println(v)
}

// Check if closed
v, ok := <-buf
if !ok {
    fmt.Println("channel closed")
}

Select Statement

select permite que uma goroutine espere em múltiplas operações de channel — escolhe a primeira pronta (aleatório se múltiplas). O case default o torna non-blocking. time.After() cria channels de timeout. Select é o coração do Go concorrente: event loops, fan-in/fan-out, timeouts. Sempre inclua timeouts para evitar deadlocks.

go
// Select: multiplex channel operations
ch1 := make(chan string)
ch2 := make(chan string)

go func() {
    time.Sleep(1 * time.Second)
    ch1 <- "one"
}()
go func() {
    time.Sleep(2 * time.Second)
    ch2 <- "two"
}()

// Wait for first to arrive
for i := 0; i < 2; i++ {
    select {
    case msg1 := <-ch1:
        fmt.Println("received:", msg1)
    case msg2 := <-ch2:
        fmt.Println("received:", msg2)
    }
}

// Timeout with select
select {
case result := <-slowOperation():
    fmt.Println(result)
case <-time.After(3 * time.Second):
    fmt.Println("timeout!")
}

// Non-blocking receive (default case)
select {
case msg := <-ch:
    fmt.Println(msg)
default:
    fmt.Println("no message") // runs if no data
}

sync.WaitGroup

sync.WaitGroup espera que um grupo de goroutines termine. Add(n) incrementa o contador, Done() o decrementa (use defer), Wait() bloqueia até zero. Sempre passe variáveis de loop como parâmetros para goroutines para evitar bugs de closure capture (corrigido no Go 1.22, mas ainda recomendado). WaitGroup é mais simples que channels para concorrência fire-and-forget.

go
import "sync"

func main() {
    var wg sync.WaitGroup

    // Launch 5 goroutines
    for i := 0; i < 5; i++ {
        wg.Add(1) // increment counter
        go func(id int) {
            defer wg.Done() // decrement when done
            fmt.Printf("Worker %d started\n", id)
            time.Sleep(time.Duration(id) * 100 * time.Millisecond)
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }

    wg.Wait() // block until all Done() called
    fmt.Println("all workers finished")
}

// Common mistake: passing loop variable to goroutine
// Always pass as parameter: go func(i int) { ... }(i)
// (Go 1.22+ fixes loop variable scoping, but still good practice)

Mutex & Sync

sync.Mutex protege estado compartilhado de acesso concorrente — Lock/Unlock com defer. RWMutex permite múltiplos leitores ou um escritor (melhor para workloads read-heavy). sync.Once garante que inicialização aconteça exatamente uma vez (padrão singleton). Prefira channels para comunicação, mutexes para proteger estado compartilhado. 'Share memory by communicating; don't communicate by sharing memory.'

go
import "sync"

// Mutex: mutual exclusion lock
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // always unlock with defer
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// RWMutex: multiple readers, one writer
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()         // read lock (multiple allowed)
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

func (c *Cache) Set(key, val string) {
    c.mu.Lock()          // write lock (exclusive)
    defer c.mu.Unlock()
    c.data[key] = val
}

// sync.Once: run exactly once
var (
    once sync.Once
    instance *Database
)

func GetDB() *Database {
    once.Do(func() {
        instance = &Database{}
    })
    return instance
}

Padrões de Concorrência

Worker pool: número fixo de goroutines processa jobs de um channel, enviando resultados para outro. Isso limita concorrência e previne exaustão de recursos. Fan-out/fan-in: distribui trabalho entre goroutines, então mescla resultados. O insight chave: feche channels quando não houver mais dados a serem enviados, para que loops range terminem. Esses padrões são a base do Go concorrente.

go
// Worker pool
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        time.Sleep(time.Second) // simulate work
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Start 3 workers
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // Send 5 jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // Collect results
    for a := 1; a <= 5; a++ {
        fmt.Println(<-results)
    }
}

// Fan-out/Fan-in
func fanOut(input <-chan int, workers int) <-chan int {
    output := make(chan int)
    var wg sync.WaitGroup
    wg.Add(workers)
    for i := 0; i < workers; i++ {
        go func() {
            defer wg.Done()
            for v := range input {
                output <- v * 2
            }
        }()
    }
    go func() { wg.Wait(); close(output) }()
    return output
}
09

File I/O & OS

Lendo Arquivos

os.ReadFile() lê um arquivo inteiro na memória (simples, mas não para arquivos grandes). Para arquivos grandes, abra com os.Open(), leia em chunks e sempre defer Close(). bufio.Scanner lê linha por linha — ideal para processamento de texto. Sempre verifique erros, especialmente io.EOF para saber quando a leitura terminou.

go
import (
    "io"
    "os"
)

// Read entire file (small files)
data, err := os.ReadFile("input.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))

// Open and read (large files)
file, err := os.Open("large.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// Read in chunks
buf := make([]byte, 1024)
for {
    n, err := file.Read(buf)
    if n > 0 {
        // process buf[:n]
    }
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
}

// Read line by line
scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    fmt.Println(line)
}

Escrevendo Arquivos

os.WriteFile() cria/trunca e escreve atomicamente (simples). os.OpenFile() com flags dá controle: O_APPEND (adicionar ao final), O_CREATE (criar se ausente), O_TRUNC (truncar). Use bufio.Writer para muitas escritas pequenas (buffers na memória, flush no final). File mode 0644: owner pode read/write, outros podem read.

go
// Write entire file (creates or truncates)
err := os.WriteFile("output.txt", []byte("Hello, World!"), 0644)
if err != nil {
    log.Fatal(err)
}

// Open for writing (with options)
file, err := os.OpenFile("log.txt",
    os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// Write
if _, err := file.WriteString("log entry\n"); err != nil {
    log.Fatal(err)
}

// Buffered writer (efficient for many small writes)
writer := bufio.NewWriter(file)
for i := 0; i < 1000; i++ {
    writer.WriteString(fmt.Sprintf("line %d\n", i))
}
writer.Flush() // don't forget to flush!

// File modes: 0644 = owner read/write, others read
// os.O_APPEND, O_CREATE, O_WRONLY, O_RDONLY, O_RDWR, O_TRUNC

Operações de Arquivo

os.Mkdir/MkdirAll criam diretórios. os.ReadDir lista entradas eficientemente. filepath.WalkDir percorre diretórios recursivamente. os.Stat dá info do arquivo (tamanho, mod time, permissões). os.IsNotExist verifica se um arquivo está ausente. os.Remove deleta um arquivo; RemoveAll deleta diretórios recursivamente. Sempre verifique erros.

go
// Create directory
os.Mkdir("mydir", 0755)       // one level
os.MkdirAll("a/b/c", 0755)    // nested directories

// List directory
entries, err := os.ReadDir(".")
if err != nil {
    log.Fatal(err)
}
for _, entry := range entries {
    fmt.Println(entry.Name(), entry.IsDir())
}

// Walk directory tree
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
    if err != nil {
        return err
    }
    fmt.Println(path)
    return nil
})

// File info
info, _ := os.Stat("file.txt")
fmt.Println(info.Size())      // bytes
fmt.Println(info.ModTime())   // last modified
fmt.Println(info.IsDir())

// Check existence
if _, err := os.Stat("file.txt"); os.IsNotExist(err) {
    fmt.Println("file does not exist")
}

// Remove
os.Remove("file.txt")
os.RemoveAll("directory") // recursive

Ambiente & Command Line

os.Getenv/LookupEnv/Setenv gerenciam variáveis de ambiente. os.Args dá argumentos raw de command-line. O pacote flag fornece flags parsed com defaults e help text. LookupEnv distingue entre valores unset e vazios. Variáveis de ambiente são a forma padrão de configurar apps 12-factor (API keys, database URLs).

go
// Environment variables
os.Setenv("API_KEY", "secret")
key := os.Getenv("API_KEY") // "" if not set

// With existence check
if val, ok := os.LookupEnv("HOME"); ok {
    fmt.Println("HOME:", val)
}

// All environment variables
for _, e := range os.Environ() {
    fmt.Println(e)
}

// Command line arguments
// args := os.Args // []string, args[0] is program name

// Using flag package
var port int
var host string
flag.IntVar(&port, "port", 8080, "server port")
flag.StringVar(&host, "host", "localhost", "server host")
flag.Parse()
fmt.Printf("Running on %s:%d\n", host, port)

// Run: go run main.go -port 3000 -host 0.0.0.0

Executando Comandos

os/exec executa comandos externos. Command() cria o comando; Output() captura stdout; Run() executa com Stdout/Stderr personalizados. Use CommandContext para timeouts (mata o processo). Sempre verifique erros — exec.ExitError indica exit codes não-zero. Cuidado com entrada do usuário para prevenir command injection.

go
import "os/exec"

// Run a command and get output
cmd := exec.Command("ls", "-la", "/tmp")
output, err := cmd.Output()
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(output))

// Capture stdout and stderr separately
cmd = exec.Command("git", "status")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = cmd.Run()
fmt.Println("stdout:", stdout.String())
fmt.Println("stderr:", stderr.String())

// Pipe input to command
cmd = exec.Command("grep", "error")
cmd.Stdin = strings.NewReader("info\nerror\nwarn\n")
result, _ := cmd.Output()
fmt.Println(string(result)) // "error"

// Run with context (timeout)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd = exec.CommandContext(ctx, "sleep", "10")
err = cmd.Run() // killed after 5s
10

JSON & Encoding

JSON Marshal & Unmarshal

encoding/json converte entre structs Go e JSON. Marshal (struct → JSON), Unmarshal (JSON → struct). Struct tags (json:"name") controlam nomenclatura e visibilidade de campos. omitempty pula valores zero/vazios. json:"-" exclui um campo inteiramente. Essa é a forma padrão de lidar com requisições/respostas de API em Go.

go
import "encoding/json"

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email,omitempty"` // omit if empty
    Password string `json:"-"`               // never serialize
    Age      int    `json:"age,omitempty"`
}

// Struct to JSON (marshal)
user := User{ID: 1, Name: "Alice", Age: 30}
data, err := json.Marshal(user)
// {"id":1,"name":"Alice","age":30}

// Pretty print
pretty, _ := json.MarshalIndent(user, "", "  ")

// JSON to struct (unmarshal)
jsonStr := `{"id":2,"name":"Bob","email":"[email protected]"}`
var u User
err := json.Unmarshal([]byte(jsonStr), &u)
fmt.Println(u.Name) // "Bob"

// Struct tags control field names:
// json:"name"        -> rename
// json:"name,omitempty" -> skip if zero value
// json:"-"           -> skip entirely
// json:",omitempty"  -> keep name, skip if empty

JSON com Maps & Slices

Para JSON dinâmico (estrutura desconhecida), faça unmarshal para map[string]any (ou interface{}). Números JSON se tornam float64 — faça type-assert para acessar. json.Decoder/Encoder trabalham com streams (arquivos, bodies HTTP) eficientemente. Use struct unmarshaling quando conhece o schema; use maps para dados flexíveis/dinâmicos.

go
// Parse arbitrary JSON into map[string]interface{}
jsonStr := `{"name":"Alice","scores":[90,85,92],"active":true}`

var result map[string]any
json.Unmarshal([]byte(jsonStr), &result)

// Type-assert to access values
name := result["name"].(string)
scores := result["scores"].([]any)
for _, s := range scores {
    fmt.Println(s.(float64)) // JSON numbers are float64
}

// Encode a map
m := map[string]any{
    "count": 42,
    "items": []string{"a", "b", "c"},
    "meta":  map[string]any{"page": 1},
}
data, _ := json.Marshal(m)

// Decode JSON stream
dec := json.NewDecoder(strings.NewReader(jsonStr))
var v map[string]any
dec.Decode(&v)

// Encode to stream
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "  ")
enc.Encode(user)

JSON Marshaling Personalizado

Implemente MarshalJSON/UnmarshalJSON para serialização personalizada. Isso é útil para: campos computados, formatos alternativos (Money como '99.99 USD'), tratamento de dados sensíveis e formatação de tempo. O method receiver para UnmarshalJSON deve ser um pointer para modificar o struct. time.Time serializa automaticamente como strings RFC 3339.

go
// Implement MarshalJSON/UnmarshalJSON
type Money struct {
    Amount   float64
    Currency string
}

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

func (m *Money) UnmarshalJSON(data []byte) error {
    var v struct {
        Amount   float64
        Currency string
    }
    if err := json.Unmarshal(data, &v); err != nil {
        return err
    }
    m.Amount = v.Amount
    m.Currency = v.Currency
    return nil
}

money := Money{Amount: 99.99, Currency: "USD"}
data, _ := json.Marshal(money)
// {"amount":99.99,"currency":"USD","display":"99.99 USD"}

// Time formatting
type Event struct {
    Time time.Time `json:"time"`
}
// time.Time marshals as RFC 3339 by default

Servidor HTTP

O pacote net/http constrói servidores HTTP. http.HandleFunc registra handlers. http.ResponseWriter escreve a resposta; *http.Request lê a requisição. Para APIs JSON, defina Content-Type e use json.NewEncoder(w).Encode(data). A biblioteca padrão é production-ready — sem framework necessário para APIs simples. Use mux routers (gorilla/mux, chi) para routing complexo.

go
import (
    "encoding/json"
    "net/http"
)

type Response struct {
    Status  string `json:"status"`
    Message string `json:"message"`
}

func main() {
    // Simple handler
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
    })

    // JSON API endpoint
    http.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
        user := User{ID: 1, Name: "Alice"}
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(user)
    })

    // Start server
    fmt.Println("Server running on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

// Request methods:
// r.Method  -> "GET", "POST", etc.
// r.URL.Query().Get("key") -> query params
// r.Body    -> request body (io.ReadCloser)

Cliente HTTP

http.Get/Post são atalhos convenientes. Para headers, métodos ou bodies personalizados, use http.NewRequest + client.Do(). Sempre defer resp.Body.Close() para evitar connection leaks. Defina um timeout no cliente (padrão é sem timeout — perigoso). Para produção, reutilize http.Client (ele faz connection-pooling) e use context para cancelamento.

go
// Simple GET
resp, err := http.Get("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
fmt.Println(string(body))

// Custom request with headers
req, err := http.NewRequest("GET", "https://api.example.com/data", nil)
req.Header.Set("Authorization", "Bearer token123")
req.Header.Set("Accept", "application/json")

resp, err := http.DefaultClient.Do(req)
defer resp.Body.Close()

// POST with JSON body
user := User{Name: "Alice"}
body, _ := json.Marshal(user)
resp, err := http.Post(
    "https://api.example.com/users",
    "application/json",
    bytes.NewBuffer(body),
)
defer resp.Body.Close()

// With timeout
client := &http.Client{Timeout: 10 * time.Second}
resp, err = client.Get("https://slow-api.example.com")
11

Testes & Benchmarking

Testes Unitários

Arquivos de teste terminam com _test.go, funções de teste começam com Test. Table-driven tests são o padrão idiomático: defina casos de teste em um slice, loop com t.Run para subtests (nomeados, individualmente executáveis). Use t.Errorf para falhas (continua), t.Fatalf para falhas fatais (para). Execute com 'go test -v' para saída verbosa.

go
// math_test.go (file must end with _test.go)
package math

import "testing"

// Test function: func TestXxx(t *testing.T)
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

// Table-driven tests (idiomatic Go)
func TestAddTable(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -1, -2},
        {"zero", 0, 0, 0},
        {"mixed", -5, 10, 5},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

// Run: go test -v
// Run specific: go test -run TestAddTable/positive

Benchmarks

Funções de benchmark começam com Benchmark e usam *testing.B. O loop b.N é ajustado pelo runtime para obter medições estáveis. Execute com 'go test -bench=.'. b.ResetTimer() exclui tempo de setup. b.ReportAllocs() mostra alocações de memória. Compare implementações com 'benchstat' para verificar se melhorias são significativas.

go
// Benchmark function: func BenchmarkXxx(b *testing.B)
func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

// Benchmark with allocations
func BenchmarkStringConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s := ""
        for j := 0; j < 100; j++ {
            s += "a"
        }
    }
}

// Run: go test -bench=.
// Output:
// BenchmarkFibonacci-8        300     4234567 ns/op
// BenchmarkStringConcat-8     200     6789012 ns/op   100 B/op   1 allocs/op

// Benchmark with setup
func BenchmarkProcess(b *testing.B) {
    data := setup() // setup not measured
    b.ResetTimer()  // start timing here
    for i := 0; i < b.N; i++ {
        process(data)
    }
}

// Memory allocations
func BenchmarkAllocs(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        make([]int, 100)
    }
}

Test Helpers & Mocking

t.Helper() melhora mensagens de erro pulando funções helper em stack traces. Mock implementando interfaces (a abordagem do Go para mocking — sem framework de mock necessário). t.Cleanup() registra funções de limpeza (como defer, mas para escopo de teste). Para mocking complexo, use testify/assert e mockery ou gomock para gerar mocks a partir de interfaces.

go
// t.Helper() marks helper functions (better error locations)
func assertEqual(t *testing.T, got, want int) {
    t.Helper()
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

// Interfaces for mocking
type DataStore interface {
    Get(key string) (string, error)
}

type MockStore struct {
    data map[string]string
}

func (m *MockStore) Get(key string) (string, error) {
    if v, ok := m.data[key]; ok {
        return v, nil
    }
    return "", errors.New("not found")
}

func TestService(t *testing.T) {
    store := &MockStore{data: map[string]string{"key": "value"}}
    svc := NewService(store)
    result := svc.GetValue("key")
    assertEqual(t, result, "value")
}

// Subtests with t.Run for setup/teardown
func TestWithCleanup(t *testing.T) {
    t.Cleanup(func() {
        // runs after test (LIFO order)
        os.Remove("tempfile")
    })
    // test code
}

Destaques da Biblioteca Padrão

A biblioteca padrão do Go é abrangente. time lida com datas/horas (nota: formatação usa reference time 2006-01-02). regexp para correspondência de padrões. context para cancelamento/timeouts através de fronteiras de goroutine. sync.Pool para reutilização de objetos (reduz pressão de GC). Go 1.21+ adiciona os pacotes slices e maps com utilidades genéricas.

go
// time package
now := time.Now()
future := now.Add(24 * time.Hour)
formatted := now.Format("2006-01-02 15:04:05")
parsed, _ := time.Parse("2006-01-02", "2024-01-15")

// regexp
re := regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)
matches := re.FindString("date: 2024-01-15")
all := re.FindAllString("2024-01-15 and 2024-02-20", -1)

// context (cancellation, timeouts)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go doWork(ctx)
// cancel() or timeout stops doWork

// sync.Pool (reuse objects)
var bufPool = sync.Pool{
    New: func() any { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)

// sort (Go 1.21+ slices package)
slices.Sort(nums)
slices.Contains(nums, 42)
slices.Reverse(nums)
12

Aprofundamento em Goroutines

Iniciando Goroutines & WaitGroup

Goroutines são threads leves gerenciadas pelo runtime do Go (não OS threads) — você pode spawnar centenas de milhares. sync.WaitGroup coordena conclusão de goroutines: Add(1) antes de iniciar, Done() quando terminar (use defer), e Wait() para bloquear até o contador chegar a zero. Sempre passe um pointer para o WaitGroup para que todas as goroutines compartilhem o mesmo contador.

go
package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // signal completion when function returns
    fmt.Printf("Worker %d started\n", id)
    // ... do work ...
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1) // increment counter before starting goroutine
        go worker(i, &wg)
    }

    wg.Wait() // block until all goroutines call Done()
    fmt.Println("All workers finished")
}

GOMAXPROCS & Scheduling

GOMAXPROCS controla quantas OS threads executam goroutines simultaneamente — o padrão é o número de CPU cores e é quase sempre ótimo. Go usa M:N scheduling: muitas goroutines em poucas OS threads. Blocking I/O ou operações de channel fazem o scheduler executar outras goroutines na mesma thread. runtime.Gosched() faz yield explícito. Você raramente precisa ajustar GOMAXPROCS.

go
package main

import (
    "fmt"
    "runtime"
)

func main() {
    // GOMAXPROCS: number of OS threads available to run goroutines
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // defaults to CPU cores

    // Set it manually (rarely needed — default is optimal)
    runtime.GOMAXPROCS(2)

    // Goroutines are multiplexed onto OS threads (M:N scheduling)
    // - Blocking syscalls (I/O) don't block other goroutines
    // - The scheduler steals work across threads
    // - A goroutine blocked on channel/IO yields its thread

    // Number of goroutines currently running
    go func() { runtime.Gosched() }() // yield to other goroutines
    fmt.Println("Goroutines:", runtime.NumGoroutine())
}

Goroutine Leaks & Prevenção

Um goroutine leak ocorre quando uma goroutine bloqueia para sempre (ex.: enviando em um channel unbuffered que ninguém lê) — ela nunca é garbage collected. Previna leaks: buffering channels, usando select com um channel/context de cancelamento, e sempre fornecendo um caminho de saída. Goroutines com leak acumulam memória e CPU. Use runtime.NumGoroutine() e pprof para detectar leaks em produção.

go
package main

// BAD: goroutine leaks — this goroutine never exits
func leaky() <-chan int {
    ch := make(chan int)
    go func() {
        ch <- 42 // blocks forever if nobody reads from ch
    }()
    return ch
}

// GOOD: use context for cancellation
func safe(ctx context.Context) <-chan int {
    ch := make(chan int, 1) // buffered — sender never blocks
    go func() {
        select {
        case ch <- 42:
        case <-ctx.Done(): // exit when context is cancelled
            return
        }
    }()
    return ch
}

// GOOD: always ensure senders can exit
// 1. Buffer the channel so send doesn't block
// 2. Use select with a done/cancel channel
// 3. Use context.WithCancel/WithTimeout for lifecycle control

sync.Once (Inicialização Única)

sync.Once garante que uma função execute exatamente uma vez em todas as goroutines — a forma padrão de implementar singletons thread-safe e lazy initialization. É mais eficiente que verificação de flag protegida por mutex. O método Do bloqueia chamadores concorrentes até a primeira invocação completar. sync.Once também é usado internamente em muitos padrões da biblioteca padrão como sync.OnceValue (Go 1.21+).

go
package main

import (
    "fmt"
    "sync"
)

var (
    instance *Database
    once     sync.Once
)

type Database struct{ name string }

func GetDB() *Database {
    // sync.Once guarantees the function runs exactly once,
    // even if called from many goroutines simultaneously
    once.Do(func() {
        instance = &Database{name: "production"}
        fmt.Println("Database initialized")
    })
    return instance
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            GetDB() // "Database initialized" prints only once
        }()
    }
    wg.Wait()
}

Worker Pool com Goroutines

O padrão worker pool limita concorrência a um número fixo de goroutines processando jobs de um channel. Isso previne exaustão de recursos de spawnar goroutines ilimitadas. Workers usam 'for job := range jobs' que sai quando o channel é fechado. close(jobs) sinaliza todos os workers para parar. Esse é o padrão canônico de concorrência do Go para paralelismo limitado.

go
package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs { // exits when jobs channel is closed
        results <- job * job // process and send result
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    var wg sync.WaitGroup

    // Start 3 workers
    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // Send 5 jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs) // signal workers to stop (range loop exits)

    wg.Wait()
    close(results)

    for r := range results {
        fmt.Println("Result:", r)
    }
}
13

Channels & Select

Básico de Channels (Unbuffered & Buffered)

Channels unbuffered (make(chan T)) sincronizam sender e receiver — o send bloqueia até um receiver estar pronto. Channels buffered (make(chan T, n)) permitem n sends sem um receiver, desacoplando timing de sender/receiver. Apenas o sender deve fechar um channel (para sinalizar 'sem mais valores'). Receber de um channel fechado retorna o zero value com ok=false. Loops range saem quando o channel é fechado.

go
package main

import "fmt"

func main() {
    // Unbuffered: send blocks until someone receives (synchronous)
    ch := make(chan int)
    go func() {
        ch <- 42 // blocks until main reads
    }()
    fmt.Println(<-ch) // 42

    // Buffered: send blocks only when buffer is full
    buf := make(chan int, 3)
    buf <- 1 // doesn't block (buffer has space)
    buf <- 2
    buf <- 3
    // buf <- 4 // would block — buffer full
    fmt.Println(<-buf) // 1 (FIFO)

    // Close a channel (sender closes, never the receiver)
    close(buf)

    // Check if closed (ok is false when closed and empty)
    val, ok := <-buf
    fmt.Println(val, ok) // 2 true

    // Range over channel until closed
    nums := make(chan int, 3)
    nums <- 10; nums <- 20; nums <- 30
    close(nums)
    for n := range nums {
        fmt.Println(n) // 10, 20, 30
    }
}

Select Statement

select permite que uma goroutine espere em múltiplas operações de channel simultaneamente — escolhe o primeiro case pronto (aleatoriamente se múltiplos estão prontos). O case default torna select non-blocking. time.After cria um channel de timeout. select é o coração da coordenação de concorrência do Go: multiplexing, timeouts, cancelamento e padrões fan-in/fan-out todos se baseiam nele.

go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "from ch1"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "from ch2"
    }()

    // select waits on multiple channel operations
    // picks the first one that's ready (random if multiple ready)
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        }
    }

    // Non-blocking receive with default
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("no message ready") // runs immediately
    }

    // Timeout with time.After
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    case <-time.After(500 * time.Millisecond):
        fmt.Println("timed out")
    }
}

Padrões Fan-In & Fan-Out

Fan-out distribui trabalho entre múltiplas goroutines para paralelismo; fan-in mescla múltiplos channels em um. Juntos eles formam um pipeline paralelo: workers fan-out processam independentemente, então fan-in coleta resultados. O fan-in usa um WaitGroup para fechar o channel mesclado apenas após todos os channels de input serem esgotados. Esses padrões são fundamentais para o processamento de dados concorrente do Go.

go
package main

import (
    "fmt"
    "sync"
)

// Fan-Out: distribute work across multiple goroutines
func fanOut(input <-chan int, n int) []<-chan int {
    outputs := make([]<-chan int, n)
    for i := 0; i < n; i++ {
        outputs[i] = process(input) // each worker reads from same input
    }
    return outputs
}

func process(input <-chan int) <-chan int {
    output := make(chan int)
    go func() {
        defer close(output)
        for v := range input {
            output <- v * v // transform
        }
    }()
    return output
}

// Fan-In: merge multiple channels into one
func fanIn(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    merged := make(chan int)

    output := func(c <-chan int) {
        defer wg.Done()
        for v := range c {
            merged <- v
        }
    }

    wg.Add(len(channels))
    for _, c := range channels {
        go output(c)
    }

    go func() {
        wg.Wait()
        close(merged)
    }()
    return merged
}

Directional Channels (Apenas Send/Receive)

Tipos de channel directional (<-chan T para receive-only, chan<- T para send-only) aplicam uso de channel em tempo de compilação. Um channel bidirecional converte implicitamente para um tipo directional quando passado a uma função. Isso documenta intenção e previne bugs — uma função producer literalmente não pode receber de seu próprio channel de output. Use tipos directionais em assinaturas de função para tornar contratos claros.

go
package main

import "fmt"

// <-chan int: receive-only (can read, cannot send)
// chan<- int: send-only (can send, cannot read)
func producer(out chan<- int) { // can only send
    for i := 0; i < 3; i++ {
        out <- i
    }
    close(out)
}

func consumer(in <-chan int) { // can only receive
    for v := range in {
        fmt.Println("consumed:", v)
    }
}

func main() {
    ch := make(chan int)

    // Directional restrictions are enforced at compile time
    go producer(ch) // bidirectional chan converts to send-only
    consumer(ch)    // bidirectional chan converts to receive-only

    // This prevents bugs: producer can't accidentally read,
    // consumer can't accidentally send or close
}

Channels Ticker & Timer

time.Ticker dispara repetidamente em intervalos — use ticker.C como um channel em select para tarefas periódicas. time.Timer dispara uma vez após uma duração. Sempre chame Stop() em tickers e timers para liberar recursos e evitar leaks. time.After é um timer one-shot conveniente que retorna um channel (mas não pode ser cancelado, então prefira NewTimer em loops select para evitar acumulação). Reset permite reagendar um timer.

go
package main

import (
    "fmt"
    "time"
)

func main() {
    // Ticker: fires repeatedly at intervals
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop() // always stop to release resources

    go func() {
        for t := range ticker.C { // ticker.C is a channel
            fmt.Println("Tick at", t)
        }
    }()

    // Timer: fires once after a duration
    timer := time.NewTimer(2 * time.Second)
    <-timer.C // blocks until timer fires
    fmt.Println("Timer fired!")

    // time.After: one-shot timer as a channel (no Stop needed)
    select {
    case <-time.After(1 * time.Second):
        fmt.Println("1 second elapsed")
    }

    // Reset a timer (cancel and reschedule)
    timer2 := time.NewTimer(5 * time.Second)
    timer2.Reset(100 * time.Millisecond)
    <-timer2.C
}
14

Pacote Context

context.WithCancel & WithTimeout

context.Context carrega cancelamento, timeouts e valores request-scoped através de fronteiras de goroutine. WithCancel retorna um context e uma função cancel; WithTimeout auto-cancela após uma duração. Goroutines verificam ctx.Done() (um channel) em um select para saber quando parar. SEMPRE chame a função cancel (use defer) para liberar recursos, mesmo se o timeout disparar — caso contrário o context vaza.

go
package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done(): // cancelled or timed out
            fmt.Printf("Worker %d stopped: %v\n", id, ctx.Err())
            return
        default:
            fmt.Printf("Worker %d working...\n", id)
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    // WithCancel: manual cancellation
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx, 1)
    time.Sleep(2 * time.Second)
    cancel() // stop the worker
    time.Sleep(500 * time.Millisecond)

    // WithTimeout: auto-cancel after duration
    ctx2, cancel2 := context.WithTimeout(context.Background(), 1500*time.Millisecond)
    defer cancel2() // always call cancel to release resources
    go worker(ctx2, 2)
    time.Sleep(2 * time.Second) // worker stops after 1.5s
}

Propagando Context Através de Chamadas

Context deve ser o primeiro parâmetro de toda função que faz I/O, e deve ser propagado por toda a cadeia de chamadas. http.Request.Context() é automaticamente cancelado quando o cliente desconecta. Passar ctx para operações de database/HTTP (QueryRowContext, NewRequestWithContext) garante que elas abortem quando o context é cancelado — prevenindo trabalho desperdiçado e leaks de recursos. Nunca armazene contexts em structs.

go
package main

import (
    "context"
    "database/sql"
    "net/http"
)

// Context should be the FIRST parameter, named ctx
func GetUser(ctx context.Context, db *sql.DB, id int) (string, error) {
    // Pass ctx to all blocking operations so they cancel together
    var name string
    err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id=?", id).Scan(&name)
    return name, err
}

func handler(w http.ResponseWriter, r *http.Request) {
    // r.Context() is cancelled when the client disconnects
    ctx := r.Context()

    name, err := GetUser(ctx, db, 42)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    w.Write([]byte(name))
}

// BEST PRACTICE: pass context through every function in the call chain
// that might do I/O. This ensures a client disconnect or timeout
// cancels ALL in-flight work (DB queries, HTTP calls, etc.)

Context Values (Dados Request-Scoped)

context.WithValue armazena dados request-scoped (como user IDs, trace IDs, auth tokens) que fluem pela cadeia de chamadas. Use um tipo de chave personalizado (não string) para evitar colisões de chave. Valores devem ser dados que a requisição precisa, não parâmetros de função — o time do Go recomenda usá-lo com parcimônia, principalmente para cross-cutting concerns como tracing/auth. Sempre faça type-assert ao recuperar valores.

go
package main

import (
    "context"
    "fmt"
    "net/http"
)

// Define a custom key type to avoid collisions
type contextKey string

const userIDKey contextKey = "userID"

// Set a value in context
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := 42 // authenticate...
        // WithValue creates a new context with the value
        ctx := context.WithValue(r.Context(), userIDKey, userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// Get a value from context (must type-assert)
func getUserID(ctx context.Context) (int, bool) {
    if v, ok := ctx.Value(userIDKey).(int); ok {
        return v, true
    }
    return 0, false
}

func handler(w http.ResponseWriter, r *http.Request) {
    if uid, ok := getUserID(r.Context()); ok {
        fmt.Fprintf(w, "User ID: %d", uid)
    }
}

Graceful Shutdown com Context

Graceful shutdown permite que requisições in-flight completem antes do servidor sair. signal.Notify captura OS signals (Ctrl+C, SIGTERM de container orchestrators). server.Shutdown(ctx) para de aceitar novas conexões e espera as ativas terminarem (até o timeout do context). Isso é essencial para servidores de produção — sem isso, requisições ativas são abruptamente terminadas, causando erros e corrupção de dados.

go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    server := &http.Server{Addr: ":8080", Handler: mux}

    // Listen for OS interrupt signals (Ctrl+C, SIGTERM)
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        log.Println("Server starting on :8080")
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    <-stop // block until signal received
    log.Println("Shutting down...")

    // Give in-flight requests 10 seconds to finish
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Printf("Forced shutdown: %v", err)
    }
    log.Println("Server stopped gracefully")
}

Context Deadline & Tratamento de Erros

context.WithDeadline cancela em um tempo absoluto; WithTimeout cancela após uma duração relativa (WithTimeout é apenas WithDeadline(now+timeout)). ctx.Err() retorna context.DeadlineExceeded ou context.Canceled para você distinguir por que parou. Use errors.Is() para verificar erros de context. Sempre verifique ctx.Err() no início de operações longas e use select com ctx.Done() durante waits bloqueantes.

go
package main

import (
    "context"
    "errors"
    "fmt"
    "time"
)

func slowOperation(ctx context.Context) (string, error) {
    // Check if already cancelled before starting
    if err := ctx.Err(); err != nil {
        return "", err
    }

    select {
    case <-time.After(3 * time.Second): // simulate slow work
        return "result", nil
    case <-ctx.Done(): // cancelled or deadline exceeded
        return "", ctx.Err()
    }
}

func main() {
    // Deadline: cancel at a specific time
    deadline := time.Now().Add(1 * time.Second)
    ctx, cancel := context.WithDeadline(context.Background(), deadline)
    defer cancel()

    result, err := slowOperation(ctx)
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            fmt.Println("Operation timed out")
        } else if errors.Is(err, context.Canceled) {
            fmt.Println("Operation was cancelled")
        }
        return
    }
    fmt.Println(result)
}
15

Servidor & Cliente HTTP

Servidor HTTP (net/http)

net/http fornece um servidor HTTP production-ready. http.HandleFunc registra handlers por caminho. Use json.NewEncoder(w).Encode() para escrever respostas JSON e json.NewDecoder(r.Body).Decode() para fazer parse de bodies de requisição. Sempre defina headers Content-Type e verifique r.Method. http.Error envia um status de erro. ListenAndServe inicia o servidor; envolva com log.Fatal para capturar erros.

go
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func getUser(w http.ResponseWriter, r *http.Request) {
    user := User{ID: 1, Name: "Alice"}

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func createUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    user.ID = 99
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

func main() {
    http.HandleFunc("/users/1", getUser)
    http.HandleFunc("/users", createUser)

    log.Println("Server on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Cliente HTTP & Requisições

O http.Client padrão NÃO tem timeout — sempre defina um para evitar travar para sempre em servidores não responsivos. Use http.NewRequestWithContext para anexar um context para cancelamento/timeouts. SEMPRE defer resp.Body.Close() para evitar connection leaks. Para produção, reutilize um único http.Client (ele gerencia connection pooling). http.Get é um atalho, mas sem timeout e personalização.

go
package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

func main() {
    // Simple GET
    resp, err := http.Get("https://api.example.com/users")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close() // ALWAYS close the body

    var users []map[string]any
    json.NewDecoder(resp.Body).Decode(&users)
    fmt.Println(users)

    // Custom client with timeout (default has NO timeout!)
    client := &http.Client{Timeout: 30 * time.Second}

    // POST with JSON body and context
    body, _ := json.Marshal(map[string]string{"name": "Bob"})
    req, _ := http.NewRequestWithContext(context.Background(),
        "POST", "https://api.example.com/users", bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")

    resp, err = client.Do(req)
    defer resp.Body.Close()
    fmt.Println("Status:", resp.Status)
}

Padrão Middleware

Middleware envolve handlers para adicionar cross-cutting concerns (logging, auth, CORS, rate limiting) sem modificar o handler em si. A assinatura func(http.Handler) http.Handler é o tipo de middleware padrão. Chain aplica-os em ordem (outermost primeiro). Esse padrão é a base de frameworks como Chi, Echo e Gin. O ResponseWriter pode ser envolvido para capturar códigos de status para logging.

go
package main

import (
    "log"
    "net/http"
    "time"
)

// Middleware wraps an http.Handler to add cross-cutting concerns
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) // call the wrapped handler
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

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

// Chain multiple middlewares
func Chain(h http.Handler, mws ...Middleware) http.Handler {
    for i := len(mws) - 1; i >= 0; i-- {
        h = mws[i](h)
    }
    return h
}

func main() {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello!"))
    })
    // Apply middlewares: Auth -> Logging -> handler
    http.Handle("/", Chain(handler, Logging, Auth))
    http.ListenAndServe(":8080", nil)
}

Servir Arquivos Estáticos & Templates

http.FileServer serve arquivos estáticos de um diretório; http.StripPrefix remove o prefixo do caminho para que caminhos de arquivo resolvam corretamente. html/template renderiza HTML com segurança (auto-escapa para prevenir XSS). template.Must entra em panic em erros de parse (fine para startup). Templates usam {{.Field}} para dados e {{range}} para iteração. Para produção, considere embeddar arquivos com go:embed em vez de ler do disco.

go
package main

import (
    "html/template"
    "net/http"
)

func main() {
    // Serve static files (CSS, JS, images)
    fs := http.FileServer(http.Dir("./static"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))

    // HTML templates
    tmpl := template.Must(template.ParseFiles("templates/index.html"))

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        data := struct {
            Title string
            Items []string
        }{
            Title: "My Page",
            Items: []string{"Apple", "Banana", "Cherry"},
        }
        tmpl.Execute(w, data)
    })

    http.ListenAndServe(":8080", nil)
}

// templates/index.html:
// <h1>{{.Title}}</h1>
// <ul>{{range .Items}}<li>{{.}}</li>{{end}}</ul>

Graceful Shutdown & go:embed

go:embed empacota arquivos no binário compilado em tempo de build — habilitando deployments true single-binary sem dependências de arquivo externo. Use diretivas //go:embed acima de uma declaração var. embed.FS é um filesystem virtual read-only. fs.Sub cria um sub-filesystem (remove o prefixo de diretório). Isso funciona para assets estáticos, templates HTML, migrations SQL, arquivos de config e mais.

go
package main

import (
    "embed"
    "io/fs"
    "net/http"
)

//go:embed static/*
var staticFiles embed.FS

func main() {
    // Embed static files into the binary (no external files needed)
    // Access the embedded filesystem
    sub, _ := fs.Sub(staticFiles, "static")
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(sub))))

    // go:embed benefits:
    // - Single binary deployment (no external files)
    // - Files are compiled into the executable
    // - Works with templates, configs, migrations, etc.

    // Embed a single file
    //go:embed config.json
    // var configData []byte

    // Embed with pattern
    //go:embed templates/*.html
    // var templates embed.FS

    http.ListenAndServe(":8080", nil)
}
16

Pacote Sync

sync.Mutex & sync.RWMutex

sync.Mutex fornece locking exclusivo — apenas uma goroutine pode mantê-lo por vez. sync.RWMutex permite múltiplos leitores concorrentes OU um escritor exclusivo — use quando leitores superam escritores em muito. Sempre pareie Lock com defer Unlock para prevenir deadlocks se a função entrar em panic. Embedde o mutex no struct (lowercase mu) para mantê-lo privado. Nunca copie um mutex (sempre use pointers).

go
package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // always unlock with defer
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// RWMutex: allows multiple readers OR one writer
type SafeCache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *SafeCache) Get(key string) (string, bool) {
    c.mu.RLock()         // read lock (multiple readers OK)
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

func (c *SafeCache) Set(key, val string) {
    c.mu.Lock()          // write lock (exclusive)
    defer c.mu.Unlock()
    c.data[key] = val
}

sync.Map (Map Concorrente)

sync.Map é um map concurrent-safe otimizado para cenários específicos: write-once-read-many (caches) ou acesso a chaves disjoint entre goroutines. Ele evita locks em leituras via operações atômicas. No entanto, para maps concorrentes de propósito geral, um map regular protegido por sync.RWMutex é frequentemente mais rápido e mais ergonômico. A API do sync.Map usa any (interface{}) para chaves e valores, perdendo type safety.

go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map

    // Store and Load (thread-safe, no locks needed)
    m.Store("name", "Alice")
    m.Store("age", 30)

    val, ok := m.Load("name")
    fmt.Println(val, ok) // Alice true

    // LoadOrStore: atomic get-or-set
    actual, loaded := m.LoadOrStore("name", "Bob")
    fmt.Println(actual, loaded) // Alice true (already existed)

    // Delete
    m.Delete("age")

    // Range over all entries
    m.Range(func(key, value any) bool {
        fmt.Printf("%v = %v\n", key, value)
        return true // continue; return false to stop
    })

    // Use sync.Map when:
    // 1. Keys are written once, read many times (caches)
    // 2. Multiple goroutines read/write disjoint keys
    // For most cases, a regular map + Mutex is simpler and faster
}

sync.Cond (Variáveis de Condição)

sync.Cond implementa variáveis de condição — goroutines esperam que uma condição se torne verdadeira. Wait() libera atomicamente o lock e dorme; Signal() acorda um waiter, Broadcast() acorda todos. Sempre use um loop for ao redor de Wait() (não if) para lidar com spurious wakeups. Cond é útil para filas producer-consumer e esperar por mudanças de estado, embora channels frequentemente forneçam uma alternativa mais simples.

go
package main

import (
    "fmt"
    "sync"
    "time"
)

type Queue struct {
    items []int
    mu    sync.Mutex
    cond  *sync.Cond
}

func NewQueue() *Queue {
    q := &Queue{}
    q.cond = sync.NewCond(&q.mu) // cond is tied to the mutex
    return q
}

func (q *Queue) Put(item int) {
    q.mu.Lock()
    defer q.mu.Unlock()
    q.items = append(q.items, item)
    q.cond.Signal() // wake one waiting goroutine
}

func (q *Queue) Get() int {
    q.mu.Lock()
    defer q.mu.Unlock()
    for len(q.items) == 0 {
        q.cond.Wait() // atomically unlocks mu, sleeps, re-locks on wake
    }
    item := q.items[0]
    q.items = q.items[1:]
    return item
}

func main() {
    q := NewQueue()
    go func() {
        time.Sleep(1 * time.Second)
        q.Put(42)
    }()
    fmt.Println("Waiting for item...")
    fmt.Println("Got:", q.Get()) // blocks until item available
}

sync.Pool (Reutilização de Objetos)

sync.Pool reutiliza objetos para reduzir alocações e pressão de GC — ideal para objetos short-lived frequentemente alocados como bytes.Buffer. Get() retorna um objeto pooled (chamando New se vazio); Put() o retorna. Pools são limpos durante GC, então não confie neles para persistência. Sempre resete objetos antes de reutilizar. A biblioteca padrão usa sync.Pool extensivamente (http, json, fmt).

go
package main

import (
    "bytes"
    "sync"
)

var bufPool = sync.Pool{
    New: func() any {
        return new(bytes.Buffer) // create when pool is empty
    },
}

func Process(data []byte) string {
    // Get a buffer from the pool (or create via New)
    buf := bufPool.Get().(*bytes.Buffer)
    defer bufPool.Put(buf) // return it when done

    buf.Reset() // clear before reuse
    buf.Write(data)
    buf.WriteString("-processed")
    return buf.String()
}

// Benefits:
// - Reduces GC pressure by reusing objects
// - Avoids allocation overhead for short-lived objects
// - Pool entries can be garbage collected between GC cycles
//
// Use for: bytes.Buffer, json encoders, temporary slices
// NOT for: long-lived objects or when size is unpredictable

errgroup (Grupo com Propagação de Erro)

errgroup (de golang.org/x/sync) é um WaitGroup que retorna o primeiro erro e pode cancelar goroutines restantes via context. g.Go() inicia uma goroutine; g.Wait() bloqueia e retorna o primeiro erro non-nil. WithContext cria um context que é cancelado quando qualquer goroutine retorna um erro — então outras goroutines param cedo. Essa é a forma idiomática de executar operações paralelas que devem todas ter sucesso ou falhar juntas.

go
package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
    "net/http"
)

// errgroup is like WaitGroup but propagates the first error
// and can cancel remaining goroutines on failure
func fetchAll(urls []string) ([]*http.Response, error) {
    g, ctx := errgroup.WithContext(context.Background())
    results := make([]*http.Response, len(urls))

    for i, url := range urls {
        i, url := i, url // capture loop variables
        g.Go(func() error {
            req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
            resp, err := http.DefaultClient.Do(req)
            if err != nil {
                return err // cancels ctx, stopping other goroutines
            }
            results[i] = resp
            return nil
        })
    }

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

func main() {
    urls := []string{
        "https://example.com",
        "https://golang.org",
    }
    resps, err := fetchAll(urls)
    fmt.Println(len(resps), err)
}
17

Generics (Go 1.18+)

Funções Genéricas & Type Parameters

Generics do Go (1.18+) usam type parameters em colchetes: [T any] declara T como qualquer tipo. O compilador infere tipos a partir de argumentos, então você raramente os especifica explicitamente. Generics habilitam funções type-safe reutilizáveis como Map/Filter/Reduce sem interface{} e type assertions. 'any' é um alias para interface{} introduzido com generics.

go
package main

import "fmt"

// Type parameters in brackets: [T any] means T can be any type
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    squared := Map(nums, func(n int) int { return n * n })
    fmt.Println(squared) // [1 4 9 16 25]

    evens := Filter(nums, func(n int) bool { return n%2 == 0 })
    fmt.Println(evens) // [2 4]

    sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
    fmt.Println(sum) // 15

    // Works with any type
    words := []string{"go", "rust", "python"}
    lengths := Map(words, func(s string) int { return len(s) })
    fmt.Println(lengths) // [2 4 6]
}

Type Constraints & comparable

Type constraints limitam quais tipos um generic aceita. 'comparable' é uma constraint integrada para tipos que suportam == (necessário para map keys e comparações). Constraints personalizadas usam type unions (int | float64) para permitir tipos específicos. O pacote golang.org/x/exp/constraints fornece Ordered (para <, >, etc.). Constraints podem combinar type sets e methods.

go
package main

import "fmt"

// comparable: types that support == and != (built-in constraint)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// Custom constraint with a type set
type Number interface {
    int | int64 | float64 | float32
}

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

// Multiple type parameters
func Pair[T, U any](first T, second U) struct{ First T; Second U } {
    return struct{ First T; Second U }{first, second}
}

func main() {
    fmt.Println(Contains([]string{"a", "b", "c"}, "b")) // true
    fmt.Println(Contains([]int{1, 2, 3}, 5))             // false

    fmt.Println(Sum([]int{1, 2, 3}))           // 6
    fmt.Println(Sum([]float64{1.5, 2.5}))      // 4

    p := Pair("Alice", 30)
    fmt.Println(p) // {Alice 30}
}

Estruturas de Dados Genéricas

Tipos genéricos (Stack[T any]) criam estruturas de dados type-safe que funcionam com qualquer tipo sem interface{} boxing ou type assertions. O type parameter T é parte do tipo, então Stack[int] e Stack[string] são tipos distintos, verificados em tempo de compilação. Isso elimina uma classe inteira de erros de tipo em runtime. Structs, methods e interfaces genéricas todos suportam type parameters.

go
package main

import "fmt"

// Generic Stack — works with any type, type-safe
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

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

func (s *Stack[T]) Len() int {
    return len(s.items)
}

func main() {
    // Type-safe stack of ints
    intStack := &Stack[int]{}
    intStack.Push(1)
    intStack.Push(2)
    val, _ := intStack.Pop()
    fmt.Println(val) // 2

    // Type-safe stack of strings
    strStack := &Stack[string]{}
    strStack.Push("hello")
    s, _ := strStack.Pop()
    fmt.Println(s) // hello

    // intStack.Push("oops") // compile error! type-safe
}

Generic Constraints com ~ (Underlying Type)

O prefixo ~ em uma constraint corresponde a qualquer tipo cujo underlying type é o named type — então ~string corresponde a tanto string quanto type MyString string. Sem ~, uma constraint corresponde apenas ao named type exato, o que raramente é útil para tipos personalizados. Use ~ quando quiser que generics funcionem com type aliases e named types derivados de primitives (comum em domain modeling).

go
package main

import "fmt"

// ~ allows any type whose UNDERLYING type matches
// (e.g., type MyString string would match ~string)
type StringLike interface {
    ~string
}

func Join[S StringLike](items []S, sep string) string {
    result := ""
    for i, item := range items {
        if i > 0 {
            result += sep
        }
        result += string(item) // convert underlying type to string
    }
    return result
}

// Numeric constraint with ~
type Integer interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

func Double[T Integer](v T) T {
    return v * 2
}

// Custom type based on int
type Score int

func main() {
    type MyString string
    items := []MyString{"a", "b", "c"}
    fmt.Println(Join(items, "-")) // a-b-c

    s := Double(Score(21))
    fmt.Println(s) // 42
}

Generics vs Interfaces (Quando Usar)

Escolha generics quando o algoritmo é idêntico entre tipos (coleções, math, transforms) — eles fornecem type safety em tempo de compilação com zero overhead de runtime. Escolha interfaces quando tipos diferentes precisam de implementações diferentes (polimorfismo) ou quando você precisa de dispatch em runtime (dependency injection, mocking). Eles são complementares: generics podem ser restringidos por interfaces (T Stringer) para o melhor dos dois mundos.

go
package main

import "fmt"

// GENERICS: best for algorithms/data structures that work
// the same way regardless of type (collections, sorting, mapping)
func Max[T int | float64](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// INTERFACES: best when different types need DIFFERENT implementations
// (polymorphism, dependency injection, mocking)
type Stringer interface {
    String() string
}

type Dog struct{ Name string }
func (d Dog) String() string { return "Dog: " + d.Name }

type Cat struct{ Name string }
func (c Cat) String() string { return "Cat: " + c.Name }

func Print[T Stringer](v T) {
    fmt.Println(v.String())
}

func main() {
    fmt.Println(Max(3, 7))       // 7
    fmt.Println(Max(3.14, 2.71)) // 3.14

    Print(Dog{"Rex"}) // Dog: Rex
    Print(Cat{"Whiskers"}) // Cat: Whiskers
}

// RULE OF THUMB:
// - Same logic, different types -> Generics
// - Different logic, shared contract -> Interfaces
// - Need runtime polymorphism -> Interfaces
// - Need compile-time type safety -> Generics
18

Reflection

reflect.Type & reflect.Value

reflect.TypeOf retorna o tipo em runtime; reflect.ValueOf retorna o valor em runtime. A partir de um Type você pode inspecionar struct fields, methods e tags. A partir de um Value você pode ler e (com CanSet) modificar fields. Reflection é poderoso, mas lento (10-100x mais lento que acesso direto) e bypassa type safety em tempo de compilação — use-o com parcimônia, principalmente para serialização, ORMs e frameworks.

go
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    u := User{Name: "Alice", Age: 30}

    // Get type and value at runtime
    t := reflect.TypeOf(u)
    v := reflect.ValueOf(u)

    fmt.Println("Type:", t.Name())        // User
    fmt.Println("Kind:", t.Kind())         // struct
    fmt.Println("NumFields:", t.NumField())

    // Iterate struct fields
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        tag := field.Tag.Get("json")
        fmt.Printf("%s (%s) = %v [json:%s]\n",
            field.Name, field.Type, value, tag)
    }
    // Name (string) = Alice [json:name]
    // Age (int) = 30 [json:age]
}

Modificando Valores com Reflection

Para modificar um valor via reflection, você deve passar um pointer e chamar .Elem() para dereferenciá-lo. CanSet() relata se um field é assignable (exported fields de um valor addressable). Unexported (lowercase) fields podem ser lidos, mas não definidos. SetInt/SetString/Set são type-checked em runtime e entram em panic em mismatch. Mutation baseada em reflection é a base de config parsers e ORMs.

go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    type Config struct {
        Port int
        Host string
    }

    cfg := Config{Port: 8080, Host: "localhost"}

    // MUST pass a pointer to modify (ValueOf of a struct is a copy)
    v := reflect.ValueOf(&cfg).Elem()

    // Check if a field is settable
    portField := v.FieldByName("Port")
    fmt.Println("Settable:", portField.CanSet()) // true

    // Modify fields by name
    portField.SetInt(9090)
    v.FieldByName("Host").SetString("0.0.0.0")

    fmt.Println(cfg) // {9090 0.0.0.0}

    // Set via interface (type-checked at runtime)
    v.FieldByName("Port").Set(reflect.ValueOf(7070))
    fmt.Println(cfg) // {7070 0.0.0.0}

    // Unexported fields are NOT settable (CanSet returns false)
}

Chamando Métodos Dinamicamente

Reflection pode chamar métodos dinamicamente por nome — útil para frameworks RPC, plugin systems e routing. MethodByName retorna um Value; Call() o invoca com um slice de argumentos reflect.Value e retorna um slice de resultados. NumMethod/Method enumeram métodos. Chamadas de método via reflection são lentas e bypassam type safety, então use-as apenas quando o nome do método não é conhecido em tempo de compilação.

go
package main

import (
    "fmt"
    "reflect"
)

type Calculator struct{}

func (c Calculator) Add(a, b int) int { return a + b }
func (c Calculator) Mul(a, b int) int { return a * b }

func main() {
    calc := Calculator{}
    v := reflect.ValueOf(calc)

    // Find a method by name
    method := v.MethodByName("Add")
    fmt.Println("Found:", method.IsValid()) // true

    // Call with arguments (each arg must be a reflect.Value)
    args := []reflect.Value{
        reflect.ValueOf(3),
        reflect.ValueOf(4),
    }
    results := method.Call(args)
    fmt.Println("3 + 4 =", results[0].Int()) // 7

    // List all methods
    t := reflect.TypeOf(calc)
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Printf("Method: %s, Type: %v\n", m.Name, m.Type)
    }
    // Method: Add, Type: func(main.Calculator, int, int) int
    // Method: Mul, Type: func(main.Calculator, int, int) int
}

Uso Prático: Struct para Map (Tag-based)

Esse padrão — iterar struct fields e ler tags — é como encoding/json, YAML parsers, ORMs e validation libraries funcionam sob o capô. reflect torna possível escrever código genérico que processa qualquer struct com base em suas tags. A convenção de tag json:'-' (pular este field) é padrão. Esse é um dos usos mais legítimos de reflection em Go.

go
package main

import (
    "fmt"
    "reflect"
)

// Convert any struct to a map using struct tags as keys
func StructToMap(obj any, tag string) map[string]any {
    result := make(map[string]any)
    v := reflect.ValueOf(obj)
    t := reflect.TypeOf(obj)

    // Dereference pointers
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
        t = t.Elem()
    }

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        // Use the tag value if present, else the field name
        key := field.Tag.Get(tag)
        if key == "" {
            key = field.Name
        }
        // Skip fields tagged with "-"
        if key == "-" {
            continue
        }
        result[key] = v.Field(i).Interface()
    }
    return result
}

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Pass string `json:"-"` // excluded
}

func main() {
    u := User{Name: "Alice", Age: 30, Pass: "secret"}
    m := StructToMap(u, "json")
    fmt.Println(m) // map[age:30 name:Alice]
}
19

Build & Tooling

go build, go run & go install

go build compila para um executável; go run compila para um arquivo temp e o executa (ótimo para desenvolvimento); go install coloca o binário em $GOPATH/bin para acesso global. O destaque do Go é cross-compilation fácil via GOOS/GOARCH — sem toolchain necessário. -ldflags='-s -w' remove info de debug para binários ~30% menores. -X injeta valores (como version strings) em tempo de build para CI/CD.

go
# Build an executable (outputs to current directory)
go build -o myapp              # custom output name
go build -o bin/myapp ./cmd    # build a specific package

# Build for different OS/arch (cross-compilation)
GOOS=linux GOARCH=amd64 go build -o myapp-linux
GOOS=windows GOARCH=amd64 go build -o myapp.exe
GOOS=darwin GOARCH=arm64 go build -o myapp-mac  # Apple Silicon

# Run without building an executable (compiles to temp, runs, deletes)
go run main.go                 # run single file
go run ./cmd/server            # run a package

# Install: builds and puts binary in $GOPATH/bin
go install ./cmd/myapp         # now 'myapp' is on your PATH

# Build flags
go build -v                    # verbose: print packages being compiled
go build -race                 # enable race detector (for testing)
go build -ldflags="-s -w"      # strip debug info (smaller binary)
go build -ldflags="-X main.Version=1.0.0"  # inject version at build time

go mod (Gerenciamento de Módulos)

go mod gerencia dependências via go.mod (lista de dependências) e go.sum (checksums para segurança). go get adiciona/atualiza deps; go mod tidy sincroniza os arquivos (execute antes de commits). Go usa Semantic Import Versioning: v2+ requer um sufixo de caminho /v2. O module cache é compartilhado em $GOPATH/pkg/mod. go mod vendor cria um diretório vendor/ para builds reproduzíveis, offline ou auditados.

go
# Initialize a new module
go mod init github.com/user/myproject

# This creates go.mod:
# module github.com/user/myproject
# go 1.21

# Add a dependency (auto-added to go.mod)
go get github.com/gin-gonic/gin@latest
go get github.com/lib/[email protected]  # specific version

# Tidy: add missing deps, remove unused ones
go mod tidy

# Download dependencies to local cache
go mod download

# Verify dependencies haven't been modified
go mod verify

# Vendor: copy deps into ./vendor (for offline builds)
go mod vendor

# Upgrade dependencies
go get -u github.com/lib/pq       # upgrade to latest minor
go get -u=patch github.com/lib/pq # upgrade patches only
go get github.com/lib/[email protected]  # pin specific version

# View dependency graph
go mod graph | head

go test & Benchmarking

go test executa arquivos _test.go; funções nomeadas TestXxx(t *testing.T) são testes, BenchmarkXxx(b *testing.B) são benchmarks. -race habilita o data race detector (essencial para código concorrente). -cover mostra cobertura de testes; -coverprofile gera um relatório detalhado. Benchmarks executam b.N iterações onde N é auto-tuned. Table-driven tests (slices de input/expected) são o estilo de teste idiomático do Go.

go
# Run all tests in the current package
go test ./...

# Verbose output
go test -v ./...

# Run a specific test
go test -run TestAdd -v

# Run benchmarks
go test -bench=. -benchmem

# Race detector (find data races)
go test -race ./...

# Coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out  # view in browser

# --- test file example (xxx_test.go) ---
# func TestAdd(t *testing.T) {
#     got := Add(2, 3)
#     if got != 5 {
#         t.Errorf("Add(2,3) = %d, want 5", got)
#     }
# }
#
# func BenchmarkAdd(b *testing.B) {
#     for i := 0; i < b.N; i++ {
#         Add(2, 3)
#     }
# }

go fmt, go vet & golangci-lint

go fmt/gofmt impõe um único formato canônico — Go não tem debates de formatação. go vet captura bugs comuns (mismatches de printf, cópias de lock, struct tags ruins). golangci-lint agrega dezenas de linters e é o padrão da indústria para CI. Execute gofmt e go vet antes de cada commit; adicione golangci-lint ao CI para análise mais profunda. Tooling consistente é uma grande razão pela qual codebases Go parecem uniformes.

go
# go fmt: format code (the ONE true Go style)
go fmt ./...              # format all files
gofmt -d main.go          # show diff without changing
gofmt -w main.go          # write changes in place

# go vet: static analysis for common mistakes
go vet ./...              # check all packages
# Catches: printf format mismatches, unreachable code,
# struct tag errors, lock copies, shadowed variables

# golangci-lint: meta-linter (runs many linters)
# Install: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run         # run all enabled linters
golangci-lint run --enable=gosec,goconst  # enable specific linters

# Common linters:
# errcheck    - check unchecked errors
# gosec       - security issues
# govet       - go vet checks
# staticcheck - advanced static analysis
# ineffassign - detect ineffectual assignments
# unused      - find unused code

# goimports: fmt + auto-manage imports
# go install golang.org/x/tools/cmd/goimports@latest
goimports -w main.go

Profiling & pprof

pprof é o profiler integrado do Go — CPU, memory, goroutine e mutex profiling. Para servidores long-running, import _ 'net/http/pprof' para expor um endpoint /debug/pprof/ para profiling ao vivo sem restarts. go tool pprof dá um shell interativo (top, list, web) ou uma web UI. O execution tracer (go tool trace) visualiza scheduling de goroutine e blocking. Profiling é essencial para código Go performance-critical.

go
# CPU profiling
go test -cpuprofile=cpu.prof -bench=.
go tool pprof cpu.prof
# Interactive commands: top, list FunctionName, web (graphviz)

# Memory profiling
go test -memprofile=mem.prof -bench=.
go tool pprof mem.prof

# Built-in HTTP pprof endpoint (for production servers)
import _ "net/http/pprof"
# Then visit: http://localhost:8080/debug/pprof/
# Capture a 30-second CPU profile:
# go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30

# In code:
import "runtime/pprof"
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// ... code to profile ...

# Trace (execution tracer)
go test -trace=trace.out -bench=.
go tool trace trace.out  # opens in browser
20

Banco de Dados (SQL)

Básico de database/sql

database/sql é a interface padrão para bancos de dados SQL. O driver é importado por seus side effects (registrando-se). sql.Open não estabelece conexão; use Ping para verificar. Sempre defer db.Close().

go
import "database/sql"
import _ "github.com/lib/pq"
db, err := sql.Open("postgres", "host=localhost dbname=mydb")
if err != nil { log.Fatal(err) }
defer db.Close()
err = db.Ping()

Consultar Rows

Query retorna múltiplas rows. Sempre defer rows.Close() para liberar recursos. Scan copia valores de colunas para variáveis. Verifique rows.Err() após o loop para erros de iteração.

go
rows, err := db.Query("SELECT id, name FROM users WHERE age > $1", 18)
if err != nil { log.Fatal(err) }
defer rows.Close()
for rows.Next() {
    var id int; var name string
    if err := rows.Scan(&id, &name); err != nil { log.Fatal(err) }
    fmt.Printf("%d: %s\n", id, name)
}

Consultar Row Único

QueryRow retorna uma única row. Scan retorna sql.ErrNoRows se nenhuma row corresponde. Mais limpo que Query para lookups de row única. Sempre trate ErrNoRows explicitamente.

go
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = $1", 1).Scan(&name)
if err != nil {
    if err == sql.ErrNoRows { fmt.Println("Not found") } else { log.Fatal(err) }
}

Prepared Statements

Prepare cria um statement reutilizável, melhorando performance para queries repetidas. Previne SQL injection. Sempre defer stmt.Close(). Use para queries executadas múltiplas vezes.

go
stmt, err := db.Prepare("INSERT INTO users(name) VALUES($1)")
if err != nil { log.Fatal(err) }
defer stmt.Close()
_, err = stmt.Exec("Alice")
_, err = stmt.Exec("Bob")

Transações

Begin inicia uma transação. Todas as operações dentro de tx são atômicas. defer Rollback é seguro: é um no-op após Commit. Se qualquer operação falhar, Rollback desfaz todas as mudanças.

go
tx, err := db.Begin()
if err != nil { log.Fatal(err) }
defer tx.Rollback()  // Safe to call after commit
_, err = tx.Exec("UPDATE accounts SET bal = bal - 100 WHERE id = 1")
_, err = tx.Exec("UPDATE accounts SET bal = bal + 100 WHERE id = 2")
if err != nil { log.Fatal(err) }
err = tx.Commit()
21

Aprofundamento em Testes

Teste Básico

Funções de teste começam com Test e recebem *testing.T. t.Errorf registra falha e continua. t.Fatalf registra e para. Execute com go test. Use testify/assert para asserções mais limpas.

go
func TestAdd(t *testing.T) {
    got := Add(2, 3)
    want := 5
    if got != want {
        t.Errorf("Add(2,3) = %d; want %d", got, want)
    }
}

Table-Driven Tests

Table-driven tests são idiomáticos em Go. Defina casos de teste como um slice de structs. Itere e execute cada um. t.Run cria subtests para falhas individuais.

go
func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {1, 2, 3}, {-1, 1, 0}, {0, 0, 0},
    }
    for _, tt := range tests {
        got := Add(tt.a, tt.b)
        if got != tt.want {
            t.Errorf("Add(%d,%d)=%d; want %d", tt.a, tt.b, got, tt.want)
        }
    }
}

Subtests

Subtests usam t.Run com um nome e função. Execute subtests específicos com -run pattern. Fornece melhor organização e saída de testes.

go
func TestAdd(t *testing.T) {
    t.Run("positive", func(t *testing.T) {
        if Add(1, 2) != 3 { t.Error("failed") }
    })
    t.Run("negative", func(t *testing.T) {
        if Add(-1, -2) != -3 { t.Error("failed") }
    })
}
// Run specific: go test -run TestAdd/negative

Test Main

TestMain executa uma vez para o pacote, substituindo o test runner padrão. Use para setup/teardown global. Deve chamar m.Run() para executar testes. os.Exit propaga o exit code.

go
func TestMain(m *testing.M) {
    setup()
    code := m.Run()
    teardown()
    os.Exit(code)
}
func setup() { /* initialize DB, etc. */ }
func teardown() { /* cleanup */ }

Mocking Interfaces

Mocking em Go depende de interfaces. Defina uma interface, implemente um mock e injete-o. Ferramentas como mockery e mockgen auto-geram mocks. Habilita testes unitários sem dependências externas.

go
type Store interface { Get(id int) (string, error) }
type MockStore struct { data map[int]string }
func (m *MockStore) Get(id int) (string, error) {
    if v, ok := m.data[id]; ok { return v, nil }
    return "", errors.New("not found")
}
// Use in tests by injecting MockStore
22

Benchmarking

Benchmark Básico

Funções de benchmark começam com Benchmark e recebem *testing.B. b.N é ajustado pelo framework para obter medições estáveis. Execute com go test -bench. Saída mostra ns/op (nanossegundos por operação).

go
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}
// Run: go test -bench=.
// Output: BenchmarkAdd-8    1000000000    0.3 ns/op

Sub-Benchmarks

Sub-benchmarks usam b.Run. ResetTimer exclui tempo de setup. ReportAllocs mostra alocações de memória. Compare performance entre tamanhos de entrada.

go
func BenchmarkSort(b *testing.B) {
    sizes := []int{100, 1000, 10000}
    for _, size := range sizes {
        b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) {
            data := generateData(size)
            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                Sort(data)
            }
        })
    }
}

Alocações de Memória

ReportAllocs mostra alocações de memória por operação. Reduzir alocações é chave para performance em Go. Use sync.Pool, pré-aloque slices e evite concatenação de strings desnecessária.

go
func BenchmarkConcat(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        s := "a" + "b" + "c"
        _ = s
    }
}
// Output includes allocs/op and B/op

Benchmarks Paralelos

RunParallel executa benchmarks concorrentemente em múltiplas goroutines. Útil para testar código thread-safe. pb.Next() itera a distribuição de trabalho. Mede throughput sob concorrência.

go
func BenchmarkParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Add(2, 3)
        }
    })
}

Comparação de Benchmarks

benchstat compara resultados de benchmark estatisticamente. Execute benchmarks múltiplas vezes com -count=10 para comparação confiável. Ajuda verificar melhorias de performance e detectar regressões.

go
# Run with memory stats
benchstat old.txt new.txt
# Compare two runs
# name      old time/op  new time/op  delta
# Add-8     2.3ns        1.8ns        -21.7%
23

Profiling (pprof)

CPU Profiling

StartCPUProfile escreve o profile de CPU para um arquivo. StopCPUProfile faz flush dos dados. Analise com go tool pprof. Use comandos top, list, web no pprof. Foque em funções que consomem mais CPU.

go
import "runtime/pprof"
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// Run your code here
// Analyze: go tool pprof cpu.prof

Memory Profiling

WriteHeapProfile captura o estado atual do heap. Chame runtime.GC() primeiro para resultados precisos. Defina MemProfileRate = 1 para profilear cada alocação (mais lento, mas preciso).

go
// Add to code
runtime.GC()
f, _ := os.Create("mem.prof")
pprof.WriteHeapProfile(f)
f.Close()
// Or use runtime.MemProfileRate = 1 for all allocations

HTTP Profiling

net/http/pprof registra endpoints de profiling no mux padrão. Acesse profiles via HTTP sem reiniciar. Útil para diagnósticos de produção. Proteja o endpoint em produção.

go
import _ "net/http/pprof"
go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Analyze live:
// go tool pprof http://localhost:6060/debug/pprof/profile

Comandos pprof

top mostra funções que consomem mais recursos. list mostra código-fonte anotado. web abre um call graph SVG. tree mostra a hierarquia de chamadas. Use focus para filtrar.

go
# Start interactive pprof
go tool pprof cpu.prof
(pprof) top 10        # Top functions by CPU
(pprof) list Add      # Show source with timings
(pprof) web           # Open graph in browser
(pprof) tree          # Call tree
(pprof) png > out.png # Save graph as PNG

Trace

runtime/trace captura traces de execução: scheduling de goroutine, GC, blocking de syscall. go tool trace abre uma web UI. Útil para diagnosticar latência e problemas de concorrência.

go
import "runtime/trace"
f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()
// Run code
// View: go tool trace trace.out
24

Build Tags

Build Tags Básicos

Build tags compilam arquivos condicionalmente. A sintaxe //go:build (Go 1.17+) substitui // +build. Tags podem ser OS (linux, darwin, windows), arch (amd64, arm64) ou personalizadas.

go
//go:build linux
// +build linux

package main
// This file only compiles on Linux

Múltiplas Tags

&& (vírgula na sintaxe antiga) requer todas as tags. || (espaço na sintaxe antiga) requer qualquer tag. ! nega. Use para implementações específicas de plataforma.

go
//go:build linux && amd64
// +build linux,amd64

//go:build linux || darwin
// +build linux darwin

//go:build !windows
// +build !windows

Tags Personalizadas

Tags personalizadas habilitam recursos opcionais. Build com flag -tags. Uso comum: builds de debug, recursos experimentais, edições enterprise vs community. Mantenha nomes de tag em lowercase.

go
//go:build debug
// +build debug

package main
const DebugMode = true
// Build with: go build -tags debug

Sufixos de Arquivo

Sufixos de arquivo fornecem constraints de build implícitas. _linux, _darwin, _windows para OS. _amd64, _arm64 para arquitetura. Mais simples que tags para código específico de plataforma.

go
// file: utils_linux.go    -> Linux only
// file: utils_darwin.go   -> macOS only
// file: utils_windows.go  -> Windows only
// file: utils_amd64.go    -> amd64 only
// file: utils_debug.go    -> requires -tags debug

Build com Tags

Use -tags para habilitar compilação condicional. Múltiplas tags são separadas por espaço. Tags de versão do Go (go1.18) habilitam código específico de versão. Verifique com go list -tags.

go
# Build with debug tag
go build -tags debug
# Multiple tags
go build -tags "debug verbose"
# List available tags
go list -tags ./...
# Conditional in code
//go:build go1.18  // Requires Go 1.18+
25

Básico de CGO

CGO Básico

CGO habilita chamar C a partir do Go. Código C está em comentários acima de import "C". O import deve estar imediatamente após o comentário. CGO torna builds mais lentos e impede cross-compilation.

go
/*
#include <stdio.h>
void hello() {
    printf("Hello from C!\n");
}
*/
import "C"
func main() {
    C.hello()
}

Passando Strings

C.CString aloca uma string C (deve liberar com C.free). CString copia os dados. Use unsafe.Pointer para conversão. Sempre libere memória alocada em C para evitar leaks.

go
/*
#include <string.h>
int len(const char* s) { return strlen(s); }
*/
import "C"
import "unsafe"
s := "Hello"cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
length := C.len(cs)

Chamando Bibliotecas C

#cgo LDFLAGS linka bibliotecas C. #cgo CFLAGS define flags do compilador. Inclua headers do sistema com #include. CGO faz ponte entre Go e bibliotecas C existentes como libm, libcrypto.

go
/*
#cgo LDFLAGS: -lm
#include <math.h>
*/
import "C"
result := float64(C.sqrt(16.0))
fmt.Println(result)  // 4

Callback de C para Go

Funções Go exportadas com //export podem ser chamadas de C. A função deve estar em package main. Habilita bibliotecas C chamarem de volta para Go. Usado em bindings FFI.

go
/*
extern void goCallback(int);
void callGo(int n) { goCallback(n); }
*/
import "C"
//export goCallback
func goCallback(n C.int) {
    fmt.Printf("Called with %d\n", n)
}

Notas de Performance

Chamadas CGO têm sobrecarga significativa comparadas a chamadas de função Go. Evite em código performance-critical. Agrupe operações para reduzir chamadas cross-boundary. Defina CGO_ENABLED=0 para builds Go puros.

go
// CGO calls have overhead (~100ns)
// Avoid in hot paths
// Batch C calls to amortize cost
// CGO disables inlining and some optimizations
// Build: CGO_ENABLED=1 go build
26

Web Frameworks

Servidor net/http

net/http é o servidor HTTP da biblioteca padrão. HandleFunc registra handlers. ListenAndServe inicia o servidor. O mux padrão é fine para apps simples; use mux personalizado para produção.

go
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
})
http.HandleFunc("/api", apiHandler)
log.Fatal(http.ListenAndServe(":8080", nil))

Framework Gin

Gin é um framework HTTP de alta performance. Param extrai parâmetros de URL. JSON serializa respostas. Gin fornece routing, middleware e validação JSON. Mais rápido que net/http devido ao httprouter.

go
import "github.com/gin-gonic/gin"
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"user": id})
})
r.Run(":8080")

Middleware

Middleware envolve handlers para adicionar cross-cutting concerns: logging, auth, CORS. c.Next() chama o próximo handler. gin.Recovery() previne crashes de panics. A ordem importa.

go
func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        fmt.Printf("%s %s %v\n", c.Request.Method, c.URL.Path, time.Since(start))
    }
}
r := gin.New()
r.Use(Logger(), gin.Recovery())

Framework Echo

Echo é outro framework popular, similar ao Gin. Handlers retornam erros para tratamento de erros centralizado. Middleware integrado para CORS, JWT, rate limiting. Design de API limpo.

go
import "github.com/labstack/echo/v4"
e := echo.New()
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    return c.JSON(200, map[string]string{"user": id})
})
e.Start(":8080")

Arquivos Estáticos

FileServer serve arquivos estáticos. StripPrefix ajusta o caminho da URL. Útil para servir HTML, CSS, JS e imagens. Para produção, use um CDN ou nginx para assets estáticos.

go
// net/http
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// Gin
r.Static("/assets", "./assets")
r.StaticFile("/favicon.ico", "./favicon.ico")
27

gRPC

Definir Proto

Protocol Buffers definem o contrato do serviço. proto3 é a sintaxe mais recente. service define métodos RPC. message define estruturas de dados. Gere código Go com protoc.

go
syntax = "proto3";
package greet;
service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }

Gerar Código

protoc gera código Go a partir de arquivos .proto. --go_out gera tipos de message. --go-grpc_out gera stubs de serviço. O código gerado não é editado manualmente.

go
# Install protoc and plugins
protoc --go_out=. --go-grpc_out=. \
    greet.proto
# This generates greet.pb.go and greet_grpc.pb.go
# Containing message types and service interfaces

Implementação do Servidor

Embedde UnimplementedGreeterServer para compatibilidade forward. Implemente os métodos do serviço. grpc.NewServer cria o servidor. Registre o serviço antes de servir.

go
type server struct { greet.UnimplementedGreeterServer }
func (s *server) SayHello(ctx context.Context, in *greet.HelloRequest) (*greet.HelloReply, error) {
    return &greet.HelloReply{Message: "Hello " + in.Name}, nil
}
lis, _ := net.Listen("tcp", ":50051")
grpc.NewServer().Serve(lis)

Cliente

grpc.Dial estabelece uma conexão. WithInsecure desabilita TLS (use credentials.NewTLS para produção). O client stub fornece métodos tipados. Conexões são pooled e reutilizadas.

go
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
client := greet.NewGreeterClient(conn)
resp, _ := client.SayHello(context.Background(), &greet.HelloRequest{Name: "Alice"})
fmt.Println(resp.Message)

Streaming

gRPC suporta três padrões de streaming. A palavra-chave stream marca streaming. Server streaming: uma requisição, muitas respostas. Bidirectional: ambos os lados fazem stream. Útil para dados em tempo real.

go
// Server streaming
rpc LotsOfReplies(HelloRequest) returns (stream HelloReply);
// Client streaming
rpc LotsOfGreetings(stream HelloRequest) returns (HelloReply);
// Bidirectional
rpc BidiHello(stream HelloRequest) returns (stream HelloReply);
28

Error Wrapping

Envolvendo Erros

Use o verbo %w para envolver erros, preservando o original. Isso cria uma cadeia de erros. Evite %v para wrapping, pois perde a cadeia. Wrapping adiciona contexto sem perder a causa raiz.

go
if err != nil {
    return fmt.Errorf("failed to open config: %w", err)
}

Unwrapping

errors.Is verifica se algum erro na cadeia corresponde. errors.As extrai um tipo de erro específico da cadeia. Use Is para valores sentinel, As para erros tipados. Ambos percorrem a cadeia de wrap.

go
err := someOperation()
if errors.Is(err, sql.ErrNoRows) {
    // Handle not found
}
var pathErr *fs.PathError
if errors.As(err, &pathErr) {
    fmt.Println("Path:", pathErr.Path)
}

Erros Personalizados

Tipos de erro personalizados implementam a interface error. Eles carregam dados estruturados para tratamento de erros. Use errors.As para extrair o tipo personalizado. Prefira erros tipados a comparações de string.

go
type ValidationError struct {
    Field string
    Msg   string
}
func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}
func validate(s string) error {
    if s == "" { return &ValidationError{Field: "name", Msg: "required"} }
    return nil
}

Erros Sentinel

Erros sentinel são variáveis de erro em nível de pacote. Use para condições de erro esperadas. Verifique com errors.Is, nunca com ==. Exporte-os para usuários verificarem.

go
var ErrNotFound = errors.New("not found")
func Find(id int) (*User, error) {
    if id > 100 { return nil, ErrNotFound }
    return &User{}, nil
}
// Check with errors.Is
if errors.Is(err, ErrNotFound) { /* ... */ }

Padrões de Tratamento de Erros

Trate erros imediatamente quando possível. Para operações deferred como Close, capture o erro. Named return values permitem que funções deferred modifiquem o valor de retorno. Sempre verifique erros de Close.

go
// Immediate handling
if err := doSomething(); err != nil {
    return fmt.Errorf("operation failed: %w", err)
}
// Deferred error checking (e.g., Close)
func readFile() (err error) {
    f, e := os.Open("file.txt")
    if e != nil { return e }
    defer func() {
        if cerr := f.Close(); err == nil { err = cerr }
    }()
    // ...
}
29

Armadilhas Comuns

Goroutine Leaks

Goroutines vazam quando bloqueiam para sempre. Sempre forneça um caminho de saída: cancelamento de context, fechar channels ou channels buffered. Use runtime.NumGoroutine() para detectar leaks.

go
// BUG: goroutine leaks if receiver stops
func send(ch chan int) {
    go func() { ch <- 1 }()  // Blocks forever
}
// FIX: use context or buffered channel
func send(ctx context.Context, ch chan int) {
    go func() {
        select {
        case ch <- 1:
        case <-ctx.Done():
        }
    }()
}

Channel Close

Apenas o sender deve fechar um channel, nunca o receiver. Fechar sinaliza sem mais valores. Receber de um channel fechado retorna o zero value. Enviar para um channel fechado entra em panic.

go
// Only the sender should close a channel
// Closing from receiver causes panic
ch := make(chan int)
go func() {
    defer close(ch)  // Sender closes
    for i := 0; i < 5; i++ { ch <- i }
}()
for v := range ch { fmt.Println(v) }

Captura de Variável de Loop

Antes do Go 1.22, variáveis de loop eram compartilhadas entre iterações. Goroutines que as capturam veem o valor final. Go 1.22+ corrige isso criando uma nova variável por iteração. Passe como parâmetro para versões mais antigas.

go
// BUG (Go < 1.22): all goroutines see last value
for i := 0; i < 3; i++ {
    go func() { fmt.Println(i) }()  // Prints 3,3,3
}
// FIX: pass as parameter
for i := 0; i < 3; i++ {
    go func(i int) { fmt.Println(i) }(i)
}

Concorrência de Map

Maps não são seguros para uso concorrente. Leitura e escrita concorrentes causam um runtime panic. Use sync.Mutex para locking explícito ou sync.Map para acesso concorrente read-heavy.

go
// BUG: concurrent map writes panic
m := map[int]int{}
go func() { m[1] = 1 }()
go func() { m[2] = 2 }()
// FIX: use sync.Map or mutex
var mu sync.Mutex
mu.Lock(); m[1] = 1; mu.Unlock()

Nil Interface

Um nil pointer envolvido em uma interface não é nil. A interface tem um tipo mesmo se o valor for nil. Sempre retorne nil diretamente, não um nil typed pointer. Verifique com reflect ou retorne nil explicitamente.

go
// BUG: nil check fails
type MyError struct{}
func (e *MyError) Error() string { return "err" }
func doSomething() error {
    var err *MyError = nil
    return err  // Non-nil interface!
}
// FIX: return nil explicitly
func doSomething() error {
    return nil
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.