A primeira coisa que surpreende programadores vindos de Python, Java ou C# ao estudar Go é a ausência de exceções. Não existe try, não existe catch, não existe throw. Em Go, erros são valores comuns — retornados por funções, armazenados em variáveis, passados como argumentos e inspecionados com if.
Essa escolha não é uma limitação. É uma decisão filosófica com consequências práticas muito positivas: o fluxo de erros se torna explícito, visível no código e auditável. Não existe a possibilidade de um erro "escapar" sem ser tratado pelo menos uma vez — o compilador obriga o programador a receber o valor de erro, mesmo que a decisão seja descartá-lo com _.
A interface error
O tipo error em Go é uma interface simples definida na biblioteca padrão:
type error interface {
Error() string
}
Qualquer tipo que implemente o método Error() string é um error. Essa simplicidade é o que permite que erros sejam tão flexíveis — podem carregar qualquer informação adicional necessária além da mensagem.
Criando erros simples
O pacote errors da biblioteca padrão oferece a forma mais direta de criar um valor de erro:
package main
import (
"errors"
"fmt"
)
func dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("divisão por zero")
}
return a / b, nil
}
func main() {
resultado, err := dividir(10, 2)
if err != nil {
fmt.Println("Erro:", err)
return
}
fmt.Printf("Resultado: %.2f\n", resultado) // 5.00
_, err = dividir(5, 0)
if err != nil {
fmt.Println("Erro:", err) // Erro: divisão por zero
}
}
O valor nil como erro significa ausência de falha — a operação foi bem-sucedida. A convenção é sempre retornar nil quando não há erro.
Formatando erros com fmt.Errorf
O pacote fmt oferece fmt.Errorf para criar erros com mensagens formatadas, semelhante ao fmt.Sprintf:
func buscarUsuario(id int) (*Usuario, error) {
if id <= 0 {
return nil, fmt.Errorf("id inválido: %d", id)
}
// ...
return nil, fmt.Errorf("usuário %d não encontrado", id)
}
Erros personalizados com contexto
Quando um erro precisa carregar informações estruturadas além de uma mensagem textual, cria-se um tipo personalizado que implementa a interface error:
package main
import "fmt"
type ErroValidacao struct {
Campo string
Valor any
Motivo string
}
func (e *ErroValidacao) Error() string {
return fmt.Sprintf("validação falhou no campo '%s' (valor: %v): %s",
e.Campo, e.Valor, e.Motivo)
}
type ErroHTTP struct {
Codigo int
Metodo string
URL string
}
func (e *ErroHTTP) Error() string {
return fmt.Sprintf("HTTP %d ao chamar %s %s", e.Codigo, e.Metodo, e.URL)
}
func validarIdade(idade int) error {
if idade < 0 {
return &ErroValidacao{
Campo: "idade",
Valor: idade,
Motivo: "não pode ser negativa",
}
}
if idade > 150 {
return &ErroValidacao{
Campo: "idade",
Valor: idade,
Motivo: "valor improvável",
}
}
return nil
}
func main() {
if err := validarIdade(-5); err != nil {
fmt.Println(err)
// Inspecionando o tipo concreto
if e, ok := err.(*ErroValidacao); ok {
fmt.Printf("Campo problemático: %s\n", e.Campo)
}
}
}
Wrapping: embrulhando erros com contexto
Uma prática essencial em Go é adicionar contexto ao propagar erros pela pilha de chamadas. Em vez de retornar o erro original diretamente, embrulha-se ele com informações sobre o que estava acontecendo:
func lerArquivo(caminho string) ([]byte, error) {
dados, err := os.ReadFile(caminho)
if err != nil {
return nil, fmt.Errorf("lerArquivo: falha ao ler %s: %w", caminho, err)
}
return dados, nil
}
func carregarConfig(caminho string) (*Config, error) {
dados, err := lerArquivo(caminho)
if err != nil {
return nil, fmt.Errorf("carregarConfig: %w", err)
}
// ...
}
O verbo %w — disponível desde Go 1.13 — embrulha o erro original, preservando-o para inspeção posterior. O resultado é uma cadeia de erros com contexto acumulado:
carregarConfig: lerArquivo: falha ao ler config.json: open config.json: no such file or directory
Cada camada adiciona seu contexto, formando uma trilha que facilita o diagnóstico sem ocultar a causa raiz.
errors.Is: verificando erros específicos
Com erros embrulhados, verificar err == ErrEspecifico não funciona — o erro está embrulhado em outros. A função errors.Is percorre toda a cadeia de wrapping:
package main
import (
"errors"
"fmt"
"os"
)
func lerConfig(caminho string) error {
_, err := os.ReadFile(caminho)
if err != nil {
return fmt.Errorf("lerConfig: %w", err)
}
return nil
}
func main() {
err := lerConfig("inexistente.json")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("Arquivo não encontrado — usando configuração padrão")
} else if err != nil {
fmt.Println("Erro inesperado:", err)
}
}
errors.Is verifica se algum erro na cadeia é igual ao alvo — tanto por comparação direta quanto pelo método Is(error) bool se o tipo o implementar.
errors.As: extraindo o tipo concreto da cadeia
Enquanto errors.Is verifica identidade, errors.As extrai o primeiro erro na cadeia que corresponde a um tipo específico:
package main
import (
"errors"
"fmt"
)
type ErroConexao struct {
Host string
Porta int
}
func (e *ErroConexao) Error() string {
return fmt.Sprintf("falha ao conectar em %s:%d", e.Host, e.Porta)
}
func conectar(host string, porta int) error {
return fmt.Errorf("iniciarServico: %w",
&ErroConexao{Host: host, Porta: porta})
}
func main() {
err := conectar("localhost", 5432)
var erroConn *ErroConexao
if errors.As(err, &erroConn) {
fmt.Printf("Problema de conexão detectado\n")
fmt.Printf("Host: %s\n", erroConn.Host)
fmt.Printf("Porta: %d\n", erroConn.Porta)
}
}
errors.As percorre a cadeia de wrapping e preenche a variável alvo com o erro encontrado, já com o tipo correto — sem necessidade de type assertion manual.
Erros sentinela: valores de erro pré-definidos
Erros sentinela são variáveis de erro exportadas que representam condições conhecidas. Permitem que os chamadores verifiquem condições específicas com errors.Is:
package main
import (
"errors"
"fmt"
)
// Erros sentinela exportados
var (
ErrNaoEncontrado = errors.New("registro não encontrado")
ErrAcessoNegado = errors.New("acesso negado")
ErrDadosInvalidos = errors.New("dados inválidos")
)
type BancoDados struct {
dados map[int]string
}
func (db *BancoDados) Buscar(id int, usuarioAdmin bool) (string, error) {
if !usuarioAdmin {
return "", fmt.Errorf("Buscar id=%d: %w", id, ErrAcessoNegado)
}
valor, existe := db.dados[id]
if !existe {
return "", fmt.Errorf("Buscar id=%d: %w", id, ErrNaoEncontrado)
}
return valor, nil
}
func main() {
db := &BancoDados{dados: map[int]string{1: "Ricardo", 2: "Ana"}}
_, err := db.Buscar(1, false)
fmt.Println(errors.Is(err, ErrAcessoNegado)) // true
fmt.Println(errors.Is(err, ErrNaoEncontrado)) // false
_, err = db.Buscar(99, true)
fmt.Println(errors.Is(err, ErrNaoEncontrado)) // true
fmt.Println(err) // Buscar id=99: registro não encontrado
}
Múltiplos erros: errors.Join
Desde Go 1.20, errors.Join permite combinar múltiplos erros em um único valor, útil para validações que precisam reportar todos os problemas de uma vez:
package main
import (
"errors"
"fmt"
)
type Formulario struct {
Nome string
Email string
Idade int
}
func (f Formulario) Validar() error {
var erros []error
if f.Nome == "" {
erros = append(erros, errors.New("nome é obrigatório"))
}
if f.Email == "" {
erros = append(erros, errors.New("email é obrigatório"))
}
if f.Idade < 18 {
erros = append(erros, fmt.Errorf("idade mínima é 18, recebido: %d", f.Idade))
}
return errors.Join(erros...)
}
func main() {
f := Formulario{Nome: "", Email: "", Idade: 15}
if err := f.Validar(); err != nil {
fmt.Println("Erros encontrados:")
fmt.Println(err)
}
}
panic e recover: para situações excepcionais
Go possui panic e recover, mas seu uso é reservado para situações verdadeiramente excepcionais — erros de programação irrecuperáveis, como índice fora dos limites ou desreferência de ponteiro nil. Não devem ser usados como substitutos para o fluxo normal de tratamento de erros.
panic interrompe a execução da goroutine atual e começa a desenrolar a pilha. recover pode capturar o pânico dentro de uma função defer:
package main
import "fmt"
func operacaoArriscada() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Pânico recuperado:", r)
}
}()
var s []int
fmt.Println(s[0]) // índice fora dos limites — causa pânico
}
func main() {
operacaoArriscada()
fmt.Println("Execução continua após recover")
}
A regra prática é clara: use error para falhas esperadas e recuperáveis. Reserve panic para condições que indicam bugs no código — estado corrompido, invariantes violadas, uso incorreto de APIs internas.
Boas práticas consolidadas
Adicione contexto ao propagar erros. Cada camada da aplicação deve adicionar informação relevante com %w:
return fmt.Errorf("processarPedido id=%d: %w", id, err)
Trate erros no nível correto. Um erro deve ser tratado — logado, convertido ou respondido ao usuário — apenas uma vez, na camada que tem contexto suficiente para fazer isso.
Não ignore erros com _ em código de produção. O compilador permite descartar erros com _, mas isso deve ser exceção extremamente rara com comentário explicando o motivo.
Erros sentinela para condições conhecidas. Defina variáveis de erro exportadas quando chamadores precisam distinguir condições específicas.
Tipos de erro para dados estruturados. Crie tipos personalizados quando o erro precisa carregar informações além da mensagem.
Resumo do que foi coberto
Este artigo percorreu o sistema de tratamento de erros do Go em profundidade: a interface error, criação com errors.New e fmt.Errorf, tipos de erro personalizados, wrapping com %w, inspeção com errors.Is e errors.As, erros sentinela, errors.Join para múltiplos erros e o papel restrito de panic e recover. Com esse módulo completo, o curso avança para organização de código com pacotes e módulos.
Referências e leituras complementares
-
Go Blog: Error handling and Go — Artigo fundamental sobre a filosofia de erros em Go. https://go.dev/blog/error-handling-and-go
-
Go Blog: Working with Errors in Go 1.13 — Introdução ao wrapping, errors.Is e errors.As. https://go.dev/blog/go1.13-errors
-
Go Blog: Errors are values — Rob Pike sobre como tratar erros como valores comuns. https://go.dev/blog/errors-are-values
-
Documentação do pacote errors — Referência completa de New, Is, As e Join. https://pkg.go.dev/errors
-
Go by Example: Errors — Exemplos práticos comentados de tratamento de erros. https://gobyexample.com/errors
-
Effective Go — Errors — Boas práticas oficiais para tratamento de erros. https://go.dev/doc/effective_go#errors