Erros são valores
Uma função Go que pode falhar devolve um error como último resultado. Quem chama verifica na hora.
Saída:
parsed: 42
could not parse: strconv.Atoi: parsing "forty-two": invalid syntax
Esse é o mecanismo inteiro. Não há exceções, nem try ou catch, nem fluxo de controle escondido: um erro só vai aonde o seu código o leva. O custo é a repetição visível de if err != nil. O benefício é que todo ponto de falha fica visível na página, e você decide em cada um o que acontece.
O tipo error
error é uma interface embutida com um único método:
type error interface {
Error() string
}
Qualquer tipo com um método Error() string é um erro. Um erro nil significa sucesso. Imprimir um erro com fmt.Println(err) ou %v chama Error().
Criando erros
Duas funções cobrem a maioria dos casos.
errors.New cria um erro com texto fixo. fmt.Errorf formata um, com os mesmos verbos do Printf. Por convenção, strings de erro começam com letra minúscula e não têm pontuação no final, porque normalmente ficam embutidas em mensagens mais longas: load config: open app.yaml: no such file or directory.
O padrão if err != nil
O formato idiomático é: chamar, verificar, retornar cedo. O caminho de sucesso fica na margem esquerda, e cada falha sai assim que acontece.
func loadUser(id int) (*User, error) {
row, err := db.Query(id)
if err != nil {
return nil, err
}
u, err := parseUser(row)
if err != nil {
return nil, err
}
if err := u.Validate(); err != nil {
return nil, err
}
return u, nil
}
Duas convenções para reparar:
- Em caso de erro, devolva o valor zero nos outros resultados (
nil,0,""). Quem chama não deve usá-los quandoerr != nil. if err := f(); err != nilrestringeerraoifquando a função só devolve um erro. Isso mantém o escopo externo limpo.
Evite o else depois de um retorno de erro. if err != nil { return err } else { ... } só indenta o caminho feliz sem motivo.
Acrescentando contexto ao devolver um erro
Um erro repassado sem alteração perde a história de onde veio. open config.yaml: no such file or directory não diz qual etapa da inicialização falhou. Acrescente contexto com fmt.Errorf e o verbo %w:
Saída:
start server: read config: open /etc/myapp/config.yaml: no such file or directory
true
Cada camada acrescenta o que estava fazendo, e a mensagem final se lê como uma trilha do topo da chamada até a causa. Um bom contexto nomeia a operação e a entrada: parse line 12, fetch user 42. Não acrescente "error" ou "failed" em cada nível; a mensagem já é um erro.
%w empacota: ele mantém o erro original dentro do novo, então errors.Is e errors.As ainda conseguem encontrá-lo. %v só copia o texto. Use %v quando quiser de propósito esconder um detalhe de implementação de quem chama, por exemplo para que não passem a depender do tipo de erro de um driver de banco de dados.
Verificando erros específicos: errors.Is e errors.As
Às vezes quem chama precisa reagir a um tipo de falha: um arquivo ausente significa "usar os padrões", um timeout significa "tentar de novo". Duas funções respondem a isso, e as duas olham através de todas as camadas de empacotamento.
Regras práticas:
- Compare com valores de erro predefinidos (sentinelas como
io.EOF,os.ErrNotExist,sql.ErrNoRows) usandoerrors.Is, não==. O==falha assim que o erro é empacotado. - Extraia um erro tipado com
errors.As, não com type assertion, pelo mesmo motivo.errors.Asrecebe um ponteiro para uma variável do tipo alvo. - Nunca compare pelo texto de
err.Error(). As mensagens mudam entre versões, e a comparação de texto quebra em silêncio quando isso acontece.
Definir os seus próprios erros sentinela e tipos de erro, e juntar vários erros com errors.Join, está em erros personalizados.
Trate cada erro uma única vez
Um erro deve ser tratado exatamente uma vez. Tratar significa uma destas coisas: devolvê-lo (normalmente empacotado), registrá-lo em log e seguir em frente, tentar de novo, ou transformá-lo em uma resposta para o usuário. Fazer duas delas é o bug de erro mais comum em código Go.
// Wrong: logged here, and returned, so it is logged again by every caller.
if err != nil {
log.Printf("could not fetch user: %v", err)
return err
}
// Right: add context and return. The top of the program logs once.
if err != nil {
return fmt.Errorf("fetch user %d: %w", id, err)
}
Registrar em log e devolver produz a mesma falha várias vezes nos logs, cada uma com menos contexto que a mensagem final. Deixe os erros subirem até o lugar que pode decidir o que fazer (um handler HTTP, um main, um laço de worker) e registre lá.
Onde os erros terminam
No topo do programa, algo precisa agir sobre o erro. No main, isso normalmente significa imprimi-lo e sair com um status diferente de zero:
Executar isto sem argumentos imprime error: usage: app <name> no stderr e sai com status 1 (digite um nome no painel Args para ver o outro caminho). Manter o main nesse formato, com o trabalho real em run, deixa o programa testável e garante que as instruções defer dentro de run ainda executem, já que o os.Exit pula as chamadas adiadas.
Em um servidor HTTP, o topo é o handler: ele converte o erro em um código de status e em uma mensagem segura para o cliente, e registra a mensagem detalhada para você.
Erros que você pode ignorar, e os que não pode
Às vezes ignorar um erro é o certo, mas deixe isso explícito com _, para que quem lê saiba que foi uma decisão:
_ = conn.SetDeadline(t) // best effort
Algumas chamadas não falham na prática (strings.Builder.WriteString, bytes.Buffer.Write). Outras parecem inofensivas e não são: o Close de um arquivo em que você escreveu pode reportar que os dados nunca chegaram ao disco, e json.Marshal falha com channels e funções. Na dúvida, verifique.
O linter errcheck (incluído no golangci-lint) reporta erros não verificados. O go vet sozinho não os aponta.
Erros e panics
Go também tem panic, mas ele não é um sistema de exceções. Use erros para tudo o que pode dar errado na operação normal: entrada inválida, arquivos ausentes, falhas de rede. Reserve o panic para bugs (um estado impossível, uma invariante quebrada) e para falhas na inicialização em que continuar não faz sentido. Uma biblioteca quase nunca deve causar panic através da sua API. Veja panic e recover.
Reduzindo a repetição
if err != nil é verboso, e propostas de nova sintaxe para ele foram recusadas repetidas vezes; o time do Go anunciou em 2025 que não vai mais buscar mudanças de sintaxe para o tratamento de erros. Alguns padrões reduzem o ruído dentro da própria linguagem:
- Retorne cedo e mantenha as funções pequenas. A maior parte da repetição vem de funções longas que fazem muitas etapas.
- O erro persistente (sticky error). Para uma sequência de escritas, guarde o primeiro erro em um campo da struct e faça as chamadas seguintes não fazerem nada depois que ele for definido. O
bufio.Writerfunciona assim: você verifica o erro uma vez depois doFlush. - Empacote uma vez por função. Uma closure adiada sobre um resultado nomeado pode acrescentar o mesmo contexto a todo erro que a função devolve (veja defer).
Erros comuns
- Usar um valor quando err não é nil. Verifique primeiro, depois use.
- Registrar em log e devolver. Escolha um.
- Comparar com
==depois de empacotar. Useerrors.Is. - Perder a causa com
%v. Use%w, a menos que esconder seja o objetivo. - Devolver um ponteiro nil tipado como
error.var e *MyErr; return enão é nil para quem chama. Devolva umnilliteral. - Mensagens com maiúscula ou pontuação.
errors.New("Failed to connect.")fica ruim depois de empacotado. Escrevaconnect to db: ....
Perguntas frequentes
Como funciona o tratamento de erros em Go?
Funções que podem falhar devolvem um error como último resultado. Quem chama verifica na hora: v, err := f(); if err != nil { return err }. Um error é um valor de interface comum com um só método, Error() string, e nil significa sucesso. Não existem exceções.
Go tem try/catch?
Não. Go não tem exceções nem try/catch. Falhas esperadas são devolvidas como valores error e verificadas com if err != nil. panic e recover existem, mas são para bugs de programação e estados irrecuperáveis, não para o fluxo normal de erros.
Como devolver um erro em Go?
Declare error como último resultado e devolva nil em caso de sucesso. Crie erros com errors.New("mensagem") para texto fixo ou com fmt.Errorf("reading %s: %w", name, err) para acrescentar contexto a um erro recebido. Em caso de falha, devolva valores zero nos outros resultados.
Qual a diferença entre %w e %v no fmt.Errorf?
Os dois colocam a mensagem do erro original no novo. %w também o empacota, então errors.Is e errors.As ainda conseguem encontrar o original. %v gera um erro novo só com o texto. Use %w quando quem chama pode precisar verificar a causa, e %v quando quiser escondê-la.
Como verificar qual erro foi devolvido em Go?
Use errors.Is(err, target) para comparar com um erro sentinela como io.EOF ou os.ErrNotExist, e errors.As(err, &target) para extrair um tipo de erro específico como *fs.PathError. Os dois percorrem os erros empacotados. Evite comparar strings de err.Error().