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.
Custom error types se tornam mais poderosos quando carregam contexto estruturado e encapsulam uma causa subjacente:
*QueryError satisfaz error e também participa de cadeias de error: o método Unwrap expõe a causa subjacente para que errors.Is e errors.As possam atravessá-la. Quem só precisa da mensagem vê "insert on users: duplicate key"; quem precisa reagir a campos específicos — por exemplo, para logar a operação e o nome da tabela, ou para fazer retry de um tipo específico de query — pode extrair a struct com errors.As.
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:
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.
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.
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. É a forma padrão de extrair dados estruturados de custom error types.
Usando o type QueryError definido anteriormente:
errors.As faz o unwrap pela chain automaticamente — aqui ele encontra *QueryError dentro do wrapper do fmt.Errorf sem que você precise fazer o unwrap manualmente.
O mesmo padrão funciona com qualquer struct customizada:
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: