A linguagem Go permite a criação de erros customizados possam ser definidos pelo usuário. Para tal, precisamos importar o package errors:

Sintaxe
import "errors"

Assim, podemos declarar nossos novos tipos de erros como mostrado no exemplo abaixo:

Ex 1:
func nome_erro() error {
  return errors.New("mensagem")
}

Quando utilizamos o método ou função New, criamos um erro em que único contexto do erro é uma mensagem de texto.

Sintaxe
func New(text string) error
Ex 2:
func MeuErro() error{
     return errors.New("minha mensagem de erro")
}

Como declaramos nossos erros como funções, o que já foi estudado sobre o assunto é válido para o uso com erros customizados.

Com o básico que foi discutido até agora, um erro customizado pode ser exibido como mostrado no exemplo abaixo:

Ex 3:
package main
import "fmt"
import "errors"

//erro customizado
func MeuErro() error{
     return errors.New("minha mensagem de erro")
}

//função que retorna error
func Funcao() (error) {
    return MeuErro()
}

func main() {
    erro := Funcao()
    fmt.Print("Erro:", erro)
}
Saída
Erro: minha mensagem de erro

Podemos utilizar o método Errorf do package fmt para imprimir em um string com os erros ocorridos em cascata ou aninhados. Assim, podemos simular um stacktrace das mensagens de erro ocorridos.

Sintaxe
func Errorf(format string, a ...any) error
Ex 4:
package main
import "fmt"
import "errors"

//erro customizado 
func ArgumentoInvalido1() error{
     return errors.New("Argumento inválido1")
}

//erro customizado 
func ArgumentoInvalido2() error{
     return errors.New("Argumento inválido2")
}

//1o erro lançado
func funcao1()(error) {
    return ArgumentoInvalido1() 
}

//2o erro lançado
func funcao2()(error) {
    erro := funcao1();
    return fmt.Errorf("%w\n%w", erro, ArgumentoInvalido2()) //concatenando erros
}

func main() {
    erro :=funcao2();
    fmt.Print("erro:\n",erro);
}
Saída
erro:
Argumento inválido1
Argumento inválido2

Como nossas funções precisam retornar tanto o valor esperado quanto nosso erro customizado, internamente em nossas funções devemos realizar as devidas validações para retorno de dados válidos e retorno de erro de forma adequada.

Sintaxe: erro customizado
func nome_erro() error {

  return errors.New("mensagem erro")

}
Sintaxe: validação de erro
func nome_funcao(<parametros>) (tipo_retorno, error){
   
   if(condicao){

     return resultado_valido, nil  //OK valor válido e nil como erro

   }

  //outras validações

  return valor_retorno, nome_erro() //ERRO valor qualquer e error como nome_erro
}

No exemplo abaixo, criamos o erro ArgumentoInvalido para que possamos exibi-lo quando o argumento de nossa função não é o esperado, retornando nosso erro customizado.

Ex 5:
package main
import "fmt"
import "errors"

//erro customizado
func ArgumentoInvalido() error{
     return errors.New("Argumento inválido")
}

//função que valida argumento retornando resultado e erro
func Dobro(p int) (int,error) {

    if(p != 0){ //se argumento válido
        return p*2, nil
    }
    return 0, ArgumentoInvalido()
}

func main() {
    _,erro := Dobro(0); //_ descarta

    if(erro != nil){ //validação de erro
        fmt.Print("Erro:", erro)
    }
}
Saída
Erro:Argumento inválido

O package errors contém o método Is. Com esse método, podemos comparar um erro capturado ao nosso erro customizado como alternativa ao uso de validação com nil visto acima.

Sintaxe
func Is(err, target error) bool
onde:
err: erro capturado
target: erro cusmtomizado para comparação

No exemplo abaixo, declaramos uma variável ErroArgInvalido como erro global e uma funçao para lançar esse erro e na main utilizamos função errors.Is para mostra a comparação de erro customizado.

Ex 5:
package main
import "fmt"
import "errors"

//erro customizado global
var ErroArgInvalido = errors.New("Argumento inválido")
//outros erros

func Dobro(p int) (int,error) {
    if(p != 0){
        return p*2, nil
    }
    return 0, ErroArgInvalido //retorno do erro 
}

func main() {
    _,erro := Dobro(0);

    if(errors.Is(erro, ErroArgInvalido)){ //validação com erros.Is
        fmt.Print("ArgumentoInvalido")
    }else{
        fmt.Print("OK")
    }
}
Saída
ArgumentoInvalido

Em alguns momentos, precisamos adicionar mais dados sobre os nossos erros customizados. Para isso, utilizamos o tipo struct com o qual podemos criar nossos erros:

Sintaxe
type nome_erro struct {
   //membros
}

Para a declaração de um novo tipo de erro, precisamos implementar o método Error da interface error que é mostrada abaixo:

Sintaxe
type error interface {
   Error() string
}

Definimos, por exemplo, as informações codigo e msg para armazenar o código do erro e uma mensagem de erro respectivamente.

Ex 6:
func (e MeuErro) Error() string {
   return fmt.Sprintf("codigo: %d msg %w", e.codigo, e.msg)
}

Assim, podemos declarar uma função que irá realizar as validações internas e retornar nosso erro customizado do tipo struct. Abaixo, o código final contendo os trechos de código mostrados nesta parte:

Ex 7:
package main
import "fmt"
//import "errors"

type MeuErro struct{
codigo int
msg string
}

func (e MeuErro) Error() string {
   return fmt.Sprintf("codigo: %d\nmsg: %s", e.codigo, e.msg)
}

func funcao(p int) (int, error) {
   if p < 0 {
       return 0, MeuErro {codigo: 0, msg:"valor < 0 "}
   }
   return p*2, nil
}

func main() {
    _,erro :=funcao(-1);
    fmt.Print("erro:\n",erro.Error());
}
Saída
erro:
codigo: 0
msg: valor < 0

Utilizando ponteiro e referência, podemos retornar a struct que representa nosso erro customizado e acessar suas informações como parte do tratamento para exibição. A função errors.As do package errors é utilizado para validação.

Modificamos o nosso método Error do exemplo anterior para o mostrado abaixo:

Ex 8:
//metodo da interface error
func (e MeuErro) Error() string {
   return e.msg
}

Na função que retorna o nosso erro, adicionamos o retorno de uma referência para a struct de erro:

Ex 9:
//função p/ retorno de erro
func funcao(p int) (int, error) {
   if p < 0 {
       return 0, &MeuErro {codigo: 0, msg:"valor < 0 "} //referência p/ erro
   }
   return p*2, nil
}

Em nossa main, alteramos o tratamento adicionando o método errors.As para validação do nosso erro customizado lançado e exibimos suas informações.

Sintaxe: errors.As
func As(err error, target any) bool
Ex 10:
func main() {
    _,erro :=funcao(-1);
    var mErro *MeuErro; //ponteiro
    
    if errors.As(erro, &mErro) {
       fmt.Printf("msg:%s\ncodigo:%d",mErro.msg,mErro.codigo)
    }
}

Abaixo, o código final contendo os trechos de código mostrados nesta parte:

Ex 11:
package main
import "fmt"
import "errors"

//novo tipo
type MeuErro struct{
codigo int
msg string
}

//método da interface error
func (e MeuErro) Error() string {
   return e.msg
}

//função p/ retorno de erro
func funcao(p int) (int, error) {

   if p < 0 {
       return 0, &MeuErro {codigo: 0, msg:"valor < 0 "}
   }

   return p*2, nil
}

func main() {
    _,erro :=funcao(-1);

    var mErro *MeuErro; //ponteiro
    
    if errors.As(erro, &mErro) { //referência
       fmt.Printf("msg:%s\ncodigo:%d",mErro.msg,mErro.codigo)
    }else{
       fmt.Printf("OK, sem bugs" )
    }
}
Saída
erro:
codigo: 0
msg: valor < 0
  1. 09/09/2025 - revisão 1: Ajustes: pontuais e em sintaxes
  2. 07/04/2025 - versão inicial