Pular para o conteúdo principal
Errors

Errors

8 min de leitura

Arquivado emLinguagem de Programação Goem

Aprenda como Go trata errors explicitamente por meio de valores de retorno, como criar e encapsular errors, e como compará-los com errors.Is e errors.As.

Na maioria das linguagens, errors interrompem o fluxo normal de execução. Uma exceção é lançada, a pilha se desfaz e um bloco catch em algum lugar trata o problema — muitas vezes longe de onde a falha ocorreu de fato. Go adota uma abordagem fundamentalmente diferente: errors são valores comuns, retornados e verificados como qualquer outro resultado. Não existe throw, nem catch, nem propagação implícita. Quando uma função pode falhar, ela declara isso na sua assinatura, e quem chama decide o que fazer.

A error interface

O type error é uma interface pré-declarada com um único método:

Qualquer type que implemente Error() string satisfaz error. Os types de error da standard library não têm nada de especial — eles implementam a mesma interface que você pode implementar, o que torna os errors em Go composáveis e extensíveis por design.

Criando errors

O package errors oferece a forma mais simples de criar um error a partir de uma string fixa:

Quando a mensagem precisa incluir valores em tempo de execução, use fmt.Errorf:

Ambos retornam um valor que satisfaz error. Nos casos em que quem chama precisa inspecionar os campos do error — não apenas ler sua mensagem — defina um type customizado:

*ValidationError satisfaz error porque implementa Error() string. Quem só precisa da mensagem trata esse error como qualquer outro; quem precisa dos dados estruturados pode extraí-los com errors.As, abordado mais adiante.

Tratando errors

Por convenção, uma função que pode falhar retorna error como seu último valor de retorno:

Quem chama verifica o error antes de usar o resultado:

Esse padrão if err != nil é idiomático em Go. O error é tratado imediatamente no local da chamada, não adiado para um bloco de tratamento em outro lugar. Os errors se propagam pela pilha de chamadas uma verificação explícita por vez, o que mantém o caminho de falha tão legível quanto o caminho de sucesso. Múltiplos blocos try-catch aninhados obscurecem quais operações podem falhar e como elas se relacionam; retornos explícitos tornam cada falha visível.

Duas convenções regem as strings de error: não começam com letra maiúscula e não terminam com pontuação. Errors são frequentemente compostos em mensagens maiores, e essas convenções mantêm o resultado legível:

O compilador não obriga a verificar errors

Go proíbe variáveis não utilizadas, mas atribuir um error a _ é válido — errors podem ser silenciosamente descartados. A disciplina de verificar cada error é uma convenção reforçada por code review e linters como errcheck, não pelo compilador.

Sentinel errors

Um sentinel error é uma variável exportada de nível de package que representa uma condição específica e identificável:

Uma função retorna o sentinel quando essa condição ocorre:

Quem chama compara o error retornado com o sentinel para distinguir esse caso dos demais:

A standard library usa esse padrão amplamente: io.EOF sinaliza o fim de um stream de dados; sql.ErrNoRows sinaliza que uma query não retornou linhas. Sentinel errors permitem que o código reaja a condições específicas sem precisar fazer parse de strings de error.

Sentinel errors usam var, não const

Valores de interface não podem ser constantes em Go, portanto sentinel errors são declarados como var. Isso significa que são tecnicamente mutáveis — mas mutar um sentinel quebraria qualquer código que compara contra ele. Trate-os como imutáveis por convenção.

Wrapping errors

Ao propagar um error pela pilha de chamadas, muitas vezes é necessário adicionar contexto. Sem wrapping, o error original viaja sem alterações e o caminho de chamada fica invisível:

fmt.Errorf com o verbo %w encapsula o error original enquanto adiciona uma mensagem:

O error encapsulado é preservado em uma error chain — a série de errors vinculados pelo wrapping. O error original permanece acessível através da chain; quem chama pode inspecioná-lo com errors.Is e errors.As, abordados abaixo.

Usar %v em vez de %w inclui a mensagem original na string formatada, mas quebra a chain:

Para combinar múltiplos errors independentes em um único valor, use errors.Join:

errors.Join descarta errors nil. Se todos os errors fornecidos forem nil, retorna nil.

Unwrapping errors

errors.Unwrap recupera o próximo error na chain:

errors.Unwrap chama o método Unwrap() error no valor, se ele existir. Errors criados por fmt.Errorf com %w implementam esse método automaticamente. Quando o error não tem mais chain, errors.Unwrap retorna nil.

Errors criados por errors.Join implementam Unwrap() []error em vez disso, retornando todos os errors encapsulados como um slice. errors.Unwrap não lida com essa forma — retorna nil para eles.

errors.Unwrap não percorre chains do errors.Join

errors.Unwrap segue apenas Unwrap() error. Retorna nil para errors de errors.Join, que implementam Unwrap() []error. Para pesquisar dentro de errors combinados, use errors.Is ou errors.As — eles lidam com ambas as formas de unwrapping.

Comparando errors

Comparar errors com == só funciona de forma confiável para sentinels. Uma vez que um error é encapsulado, == não encontra o original — o wrapper é um valor diferente. errors.Is e errors.As percorrem a chain inteira.

errors.Is

errors.Is(err, target) retorna true se qualquer error na chain corresponder a target:

A comparação padrão é ==. Um type de error pode substituí-la implementando um método Is(target error) bool. Quando presente, errors.Is o chama em vez de usar ==:

Com esse método, dois valores *NotFoundError com o mesmo ID são considerados iguais por errors.Is, independentemente da identidade do pointer:

errors.As

errors.As(err, target) percorre a chain e, se encontrar um error cujo type é atribuível ao type apontado por target, define target e retorna true:

target deve ser um pointer não-nil para um type que implemente error, ou para qualquer type de interface. Um type de error pode customizar o comportamento de match com um método As(target any) bool. Isso é particularmente útil quando você quer que errors.As encontre uma interface em vez de um concrete type:

Quem chama pode então extrair a interface Retryable da chain sem conhecer o concrete type: