Setup & Hello World
Go é uma linguagem compilada, estaticamente tipada e criada pelo Google em 2009. A instalação entrega um toolchain completo (go build, go test, go fmt, go vet) sem depender de gerenciadores externos. Use o mise ou baixe direto em go.dev/dl.
Instalação e primeiro módulo
# 1. Instale o Go (via mise/asdf ou pacote oficial)
mise use -g [email protected]
# 2. Verifique a versão
go version
# → go version go1.23.0 darwin/arm64
# 3. Crie a pasta do projeto e inicialize um módulo
mkdir hello && cd hello
go mod init github.com/kaique/hello
# → cria o arquivo go.mod
# 4. Estrutura mínima gerada
.
├── go.mod # manifesto do módulo (equivalente ao package.json)
└── go.sum # checksums de dependências (só aparece após go get)Hello World idiomático
// main.go — todo executável Go começa em package main + func main
package main
import "fmt"
func main() {
fmt.Println("Olá, Go 1.23!")
}# Executar sem gerar binário (ótimo para scripts)
go run .
# → Olá, Go 1.23!
# Compilar um binário nativo estático
go build -o bin/hello .
./bin/hello
# → Olá, Go 1.23!
# Formatar todo o código do projeto (padrão oficial)
go fmt ./...
# Rodar o linter embutido
go vet ./...go.mod declara o nome do módulo, a versão do Go e as dependências diretas. O go.sum grava hashes criptográficos de cada versão baixada — nunca edite à mão e sempre comite os dois arquivos.go run
Compila e executa em memória. Usado durante o desenvolvimento para feedback rápido.
go build
Gera um binário nativo estático. Deploy sem runtime — basta copiar o executável.
go mod tidy
Sincroniza o go.mod: adiciona dependências usadas e remove as órfãs.
go install
Compila e instala um binário global em $GOPATH/bin. Ótimo para CLIs.
Tipos e Variáveis
Go tem tipagem estática com inferência via :=. O zero value de cada tipo é sempre definido (0, "", false, nil) — nunca há variável "indefinida" como em JavaScript.
Declaração de variáveis
package main
import "fmt"
func main() {
// Declaração explícita com var + tipo
var nome string = "Kaique"
var idade int = 28
// Tipo inferido (dentro de funções) — forma idiomática
ativo := true
saldo := 1250.75
// Múltipla atribuição
x, y := 10, 20
// Zero values (sem inicialização)
var contador int // 0
var titulo string // ""
var pronto bool // false
var ptr *int // nil
fmt.Println(nome, idade, ativo, saldo, x, y)
fmt.Println(contador, titulo, pronto, ptr)
}Tipos primitivos fundamentais
// Inteiros — escolha o tamanho quando importa
var i8 int8 = 127 // -128..127
var i16 int16 = 32_000
var i32 int32 = 2_000_000_000
var i64 int64 = 9_000_000_000_000
// int é alias para int32 ou int64 conforme a arquitetura (use por padrão)
var n int = 42
// Não-assinados (unsigned)
var u8 uint8 = 255
var u16 uint16 = 65_535
// Ponto flutuante
var pi float64 = 3.14159 // padrão para decimais
var f32 float32 = 1.5
// Booleano
var ligado bool = true
// String imutável (UTF-8 sob o capô)
var saudacao string = "Olá, mundo"
// rune = int32 — representa um code point Unicode
var letra rune = 'K'
// byte = uint8 — usado para dados binários e ASCII
var b byte = 'A'Constantes e iota
const Pi = 3.14159
const MaxRetries int = 5
// Bloco de constantes
const (
StatusPendente = "pending"
StatusAtivo = "active"
StatusFalha = "failed"
)
// iota — gerador de inteiros sequenciais (começa em 0)
const (
Domingo = iota // 0
Segunda // 1
Terca // 2
Quarta // 3
Quinta // 4
Sexta // 5
Sabado // 6
)
// iota com expressões (flags binárias)
const (
FlagLeitura = 1 << iota // 1 (001)
FlagEscrita // 2 (010)
FlagExecucao // 4 (100)
)Conversão de tipos (explícita sempre)
// Go NÃO faz conversão implícita entre tipos numéricos
var i int = 42
var f float64 = float64(i) // precisa converter
var u uint = uint(f) // precisa converter
// var err = i + f // ❌ erro de compilação
// string ↔ []byte e string ↔ []rune
s := "Olá"
bs := []byte(s) // [79 108 195 131]
rs := []rune(s) // [79 108 227] (code points)
back := string(bs) // "Olá"
// int ↔ string (via strconv)
import "strconv"
n, err := strconv.Atoi("42") // "42" → 42
texto := strconv.Itoa(99) // 99 → "99"
_ = errrune para trabalhar com caracteres Unicode (emojis, acentos) e byte para dados binários ou ASCII puro. Iterar uma string com range entrega runes; indexar com s[i] entrega bytes.undefined. Variáveis não inicializadas recebem um valor neutro — isso elimina toda uma classe de bugs do JavaScript/Java.Controle de Fluxo
Go tem apenas uma palavra-chave de loop: for. E também dispensa parênteses em condições. A sintaxe é deliberadamente minimalista — menos formas de escrever a mesma coisa significa código mais uniforme em qualquer codebase.
if, else e atribuição curta
package main
import (
"fmt"
"strconv"
)
func main() {
idade := 20
if idade >= 18 {
fmt.Println("maior de idade")
} else if idade >= 16 {
fmt.Println("pode votar")
} else {
fmt.Println("menor")
}
// Short statement — declara e testa na mesma linha
if n, err := strconv.Atoi("42"); err == nil {
fmt.Println("número:", n) // n e err só existem aqui dentro
}
}switch — mais expressivo que em outras linguagens
func classificar(n int) string {
switch {
case n < 0:
return "negativo"
case n == 0:
return "zero"
case n < 10:
return "pequeno"
default:
return "grande"
}
}
// Switch com valor + múltiplos casos por linha
func diaUtil(dia string) bool {
switch dia {
case "sábado", "domingo":
return false
default:
return true
}
}
// Type switch — inspeciona o tipo dinâmico de interface{}
func describe(x any) string {
switch v := x.(type) {
case int:
return fmt.Sprintf("int: %d", v)
case string:
return fmt.Sprintf("string: %q", v)
case bool:
return fmt.Sprintf("bool: %t", v)
default:
return "tipo desconhecido"
}
}for — única estrutura de loop
// Clássico C-style
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// Enquanto (equivalente a while)
n := 10
for n > 0 {
n--
}
// Loop infinito + break
for {
if algumaCondicao() {
break
}
}
// range — itera slices, maps, strings, channels
nomes := []string{"Ana", "Bia", "Caio"}
for i, nome := range nomes {
fmt.Printf("%d: %s\n", i, nome)
}
// Ignorar o índice com _
for _, nome := range nomes {
fmt.Println(nome)
}
// range em string entrega runes (Unicode correto)
for i, r := range "Olá" {
fmt.Printf("byte %d = %c\n", i, r)
}
// range em map (ordem NÃO é garantida)
idades := map[string]int{"Ana": 30, "Bia": 25}
for nome, idade := range idades {
fmt.Println(nome, idade)
}
// Go 1.22+ — range sobre inteiro
for i := range 10 {
fmt.Println(i) // 0..9
}defer — execução adiada
import "os"
func lerArquivo(caminho string) error {
f, err := os.Open(caminho)
if err != nil {
return err
}
defer f.Close() // executa no return, mesmo em panic
// ... trabalho com o arquivo
return nil
}
// defers empilham em ordem LIFO (último entra, primeiro sai)
func ordem() {
defer fmt.Println("primeiro")
defer fmt.Println("segundo")
defer fmt.Println("terceiro")
// Saída: terceiro → segundo → primeiro
}try/finally. Use sempre que precisar garantir cleanup: fechar arquivos, liberar mutex, desconectar DB. Coloque o defer logo após adquirir o recurso — fica impossível esquecer.Funções e Erros
Funções em Go são cidadãos de primeira classe: podem retornar múltiplos valores, aceitar argumentos variádicos, formar closures e servir de parâmetro. O tratamento de erros é explícito via retorno (T, error) — sem exceptions.
Múltiplos retornos e named returns
package main
import (
"errors"
"fmt"
)
// Retorno múltiplo — padrão (valor, erro)
func dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("divisão por zero")
}
return a / b, nil
}
// Named returns — documenta e permite return "pelado"
func minMax(nums []int) (min, max int, err error) {
if len(nums) == 0 {
err = errors.New("slice vazio")
return
}
min, max = nums[0], nums[0]
for _, n := range nums[1:] {
if n < min {
min = n
}
if n > max {
max = n
}
}
return // retorna min, max, err (zero value)
}
func main() {
r, err := dividir(10, 2)
if err != nil {
fmt.Println("erro:", err)
return
}
fmt.Println("resultado:", r)
}Variádicas e closures
// Variádica — 0..N argumentos do mesmo tipo
func soma(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
soma() // 0
soma(1, 2, 3) // 6
nums := []int{1, 2, 3, 4}
soma(nums...) // expansão de slice com ...
// Closure — captura variáveis do escopo
func contador() func() int {
n := 0
return func() int {
n++
return n
}
}
c := contador()
fmt.Println(c(), c(), c()) // 1 2 3
// Função como parâmetro
func aplicar(nums []int, f func(int) int) []int {
out := make([]int, len(nums))
for i, n := range nums {
out[i] = f(n)
}
return out
}
dobro := aplicar([]int{1, 2, 3}, func(n int) int { return n * 2 })
// [2 4 6]Tratamento de erros idiomático
import (
"errors"
"fmt"
"os"
)
// 1. Sentinel error — valor exportado para comparação
var ErrNaoEncontrado = errors.New("usuário não encontrado")
func buscar(id string) (string, error) {
if id == "" {
return "", ErrNaoEncontrado
}
return "Kaique", nil
}
// 2. Custom error type — permite carregar contexto
type ErroHTTP struct {
Status int
Msg string
}
func (e *ErroHTTP) Error() string {
return fmt.Sprintf("HTTP %d: %s", e.Status, e.Msg)
}
// 3. Wrap de erro com %w — preserva a cadeia
func carregar() error {
_, err := os.Open("config.json")
if err != nil {
return fmt.Errorf("carregar config: %w", err)
}
return nil
}
// 4. errors.Is — compara com sentinela (mesmo após wrap)
// 5. errors.As — extrai tipo concreto da cadeia
func tratar(err error) {
if errors.Is(err, os.ErrNotExist) {
fmt.Println("arquivo não existe, criando default")
return
}
var httpErr *ErroHTTP
if errors.As(err, &httpErr) {
fmt.Printf("status %d\n", httpErr.Status)
}
}panic e recover (use com cautela)
// panic — interrompe o fluxo normal (reservado para erros irrecuperáveis)
func dividirUnsafe(a, b int) int {
if b == 0 {
panic("divisão por zero!")
}
return a / b
}
// recover — captura panic dentro de defer (contém o estrago)
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recuperado: %v", r)
}
}()
return dividirUnsafe(a, b), nil
}err != nil → retorne cedo (early return). Evite panic em código de biblioteca — reserve para bugs de programação (nil pointer, índice fora do range) ou inicialização que falhou em tempo de boot._, _ = f() em código de produção. Se não importa o erro, comente explicitamente. O linter errcheck vigia isso para você.Estruturas Compostas
Go oferece quatro estruturas compostas principais: array (tamanho fixo), slice (tamanho dinâmico), map (dicionário) e struct (registro nomeado). Structs + métodos + interfaces pequenas formam o coração do modelo de objetos de Go.
Arrays e slices
// Array — tamanho fixo na declaração (raro no dia a dia)
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{10, 20, 30} // compilador conta
// Slice — wrapper dinâmico sobre um array (estrutura principal)
nums := []int{1, 2, 3, 4, 5}
// make — aloca slice com tamanho e capacidade
s := make([]int, 3, 10) // len=3, cap=10 — pré-aloca sem realocar
// append — cresce o slice (pode realocar se cap estourar)
s = append(s, 99, 100)
// Slicing — subfatias (compartilha array subjacente!)
meio := nums[1:4] // [2 3 4]
// len e cap
fmt.Println(len(s), cap(s))
// Cópia profunda
dst := make([]int, len(nums))
copy(dst, nums)Maps (hash tables)
// Literal
idades := map[string]int{
"Ana": 30,
"Bia": 25,
}
// make — alocação explícita
m := make(map[string]int)
m["Caio"] = 40
// Acesso com "comma-ok" — distingue zero value de ausência
if idade, ok := idades["Ana"]; ok {
fmt.Println("Ana tem", idade)
} else {
fmt.Println("Ana não está no map")
}
// Remover entrada
delete(idades, "Ana")
// Iterar (ordem aleatória a cada execução — by design)
for nome, idade := range idades {
fmt.Println(nome, idade)
}
// Map vazio ≠ nil
var vazio map[string]int // nil — escrita causa panic!
usavel := map[string]int{} // usável
outro := make(map[string]int) // idemStructs e métodos
// Struct — registro tipado (equivalente a class sem herança)
type Usuario struct {
ID string
Nome string
Email string
Idade int
}
// Instanciação
u1 := Usuario{ID: "1", Nome: "Kaique", Email: "[email protected]", Idade: 28}
u2 := Usuario{} // todos os campos com zero value
u3 := &Usuario{Nome: "Ana"} // ponteiro para heap
// Acesso com ponto (mesmo em ponteiro — auto-deref)
fmt.Println(u1.Nome, u3.Nome)
// Método com value receiver — opera numa cópia
func (u Usuario) Saudacao() string {
return "Olá, " + u.Nome
}
// Método com pointer receiver — permite mutação
func (u *Usuario) Envelhecer() {
u.Idade++
}
u1.Envelhecer() // Go auto-converte para (&u1).Envelhecer()
fmt.Println(u1.Saudacao())Embedding (composição sem herança)
type Timestamp struct {
CriadoEm time.Time
AtualizadoEm time.Time
}
// Embedding — Artigo "herda" campos e métodos de Timestamp
type Artigo struct {
Timestamp // campo anônimo
Titulo string
Conteudo string
}
a := Artigo{
Timestamp: Timestamp{CriadoEm: time.Now()},
Titulo: "Go é simples",
}
fmt.Println(a.CriadoEm) // acesso direto (promovido)Interfaces pequenas
// Interface = contrato (conjunto de métodos)
// Go é ESTRUTURALMENTE tipado: quem implementa os métodos, satisfaz a interface
type Saudador interface {
Saudacao() string
}
// Usuario já implementa Saudacao — nenhuma declaração "implements" necessária
var s Saudador = u1
fmt.Println(s.Saudacao())
// Interface vazia — any em Go 1.18+
func debug(x any) {
fmt.Printf("%T: %v\n", x, x)
}
// Type assertion — extrair tipo concreto
var i any = "hello"
str, ok := i.(string) // "hello", true
n, ok := i.(int) // 0, falseio.Reader, io.Writer, fmt.Stringer). Quanto menor a interface, mais fácil de implementar e mockar. O provérbio de Go: "accept interfaces, return structs".Value vs pointer receiver
Use pointer (*T) quando o método muda o estado ou o struct é grande. Value quando é imutável e pequeno.
Tags de struct
Metadados para reflection: json:"nome", bson:"_id", validate:"required".
Concorrência Básica
Concorrência é a feature estrela de Go. Goroutines são threads ultraleves (2KB de stack inicial) e channels permitem comunicação sem shared memory. O lema: "Don't communicate by sharing memory; share memory by communicating."
Goroutines
package main
import (
"fmt"
"time"
)
func trabalhar(id int) {
fmt.Printf("worker %d começou\n", id)
time.Sleep(200 * time.Millisecond)
fmt.Printf("worker %d terminou\n", id)
}
func main() {
// go + chamada = dispara em goroutine (assíncrono)
for i := 1; i <= 3; i++ {
go trabalhar(i)
}
// main não espera goroutines — precisamos sincronizar
time.Sleep(500 * time.Millisecond)
fmt.Println("fim")
}Channels — comunicação tipada
// Channel unbuffered — envio e recepção bloqueiam até sincronizar
ch := make(chan int)
go func() {
ch <- 42 // envia
}()
valor := <-ch // recebe
fmt.Println(valor) // 42
// Channel buffered — até N envios não bloqueiam
buf := make(chan string, 3)
buf <- "a"
buf <- "b"
buf <- "c"
// buf <- "d" // bloqueia (buffer cheio)
close(buf) // sinaliza fim
// range consome até o channel ser fechado
for msg := range buf {
fmt.Println(msg) // a, b, c
}
// Comma-ok detecta channel fechado
v, ok := <-buf
if !ok {
fmt.Println("channel fechado")
}select — multiplexação de channels
func consumirComTimeout(dados <-chan string) {
select {
case msg := <-dados:
fmt.Println("recebi:", msg)
case <-time.After(2 * time.Second):
fmt.Println("timeout!")
}
}
// select com default — não-bloqueante
func tryReceive(ch <-chan int) {
select {
case v := <-ch:
fmt.Println("valor:", v)
default:
fmt.Println("nada disponível agora")
}
}sync.WaitGroup — esperar goroutines terminarem
import "sync"
func baixarTudo(urls []string) {
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1) // incrementa ANTES da goroutine
go func(u string) {
defer wg.Done() // decrementa ao terminar
fmt.Println("baixando", u)
time.Sleep(300 * time.Millisecond)
}(url) // passa url por valor para evitar captura buggy
}
wg.Wait() // bloqueia até contador zerar
fmt.Println("tudo pronto")
}sync.Mutex — proteção de estado compartilhado
type Contador struct {
mu sync.Mutex
n int
}
func (c *Contador) Incrementa() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}
func (c *Contador) Valor() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.n
}
func main() {
c := &Contador{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Incrementa()
}()
}
wg.Wait()
fmt.Println("total:", c.Valor()) // 1000 (sempre)
}context.Context — cancelamento e deadlines
import (
"context"
"fmt"
"time"
)
func tarefaLonga(ctx context.Context) error {
for i := 0; i < 10; i++ {
select {
case <-ctx.Done():
return ctx.Err() // cancelled / deadline exceeded
case <-time.After(500 * time.Millisecond):
fmt.Println("passo", i)
}
}
return nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := tarefaLonga(ctx); err != nil {
fmt.Println("encerrada:", err)
}
}context.Context deve ser o primeiro parâmetro da função (ctx context.Context). Toda chamada I/O (HTTP, DB, cache) deve aceitar ctx para permitir cancelamento propagado.context, select com default ou feche channels explicitamente.