← Voltar para o blog

errors.AsType no Go 1.26: Tratamento de Erros Type-Safe

Aprenda a usar errors.AsType no Go 1.26 para tratamento de erros type-safe. Compare com errors.As, veja exemplos praticos e migre seu codigo com go fix.

O Go 1.26 trouxe uma das melhorias mais aguardadas no tratamento de erros: a funcao errors.AsType. Depois de anos de discussao na comunidade – a issue original no GitHub data de 2022 – finalmente temos uma forma type-safe de inspecionar erros sem recorrer a ponteiros e reflexao. Se voce trabalha com tratamento de erros em Go, essa mudanca vai simplificar significativamente seu codigo.

O Problema com errors.As

Desde o Go 1.13, errors.As e a forma padrao de verificar se um erro pertence a um tipo especifico. O problema e que a API exige um ponteiro pre-alocado e usa reflexao internamente:

func processarArquivo(caminho string) error {
	data, err := os.ReadFile(caminho)
	if err != nil {
		var pathErr *fs.PathError
		if errors.As(err, &pathErr) {
			fmt.Printf("Erro no caminho: %s\n", pathErr.Path)
			fmt.Printf("Operacao: %s\n", pathErr.Op)
			return fmt.Errorf("arquivo inacessivel: %w", err)
		}
		return err
	}
	// processa data...
	return nil
}

Esse codigo funciona, mas tem problemas sutis:

  1. Verbosidade – voce precisa declarar a variavel pathErr antes de usa-la
  2. Reflexao em runtimeerrors.As usa reflect para validar tipos, com custo de performance
  3. Risco de panic – se voce passar um tipo errado (nao-ponteiro para interface), o programa entra em panic
  4. Escopo da variavelpathErr fica acessivel fora do bloco if, poluindo o escopo

Esses problemas nao sao criticos em codigo simples, mas se acumulam em projetos grandes com dezenas de tipos de erro customizados.

errors.AsType: A Solucao Type-Safe

errors.AsType e uma funcao generica que elimina todos esses problemas:

func processarArquivo(caminho string) error {
	data, err := os.ReadFile(caminho)
	if err != nil {
		if pathErr, ok := errors.AsType[*fs.PathError](err); ok {
			fmt.Printf("Erro no caminho: %s\n", pathErr.Path)
			fmt.Printf("Operacao: %s\n", pathErr.Op)
			return fmt.Errorf("arquivo inacessivel: %w", err)
		}
		return err
	}
	// processa data...
	return nil
}

A assinatura da funcao e:

func AsType[E error](err error) (E, bool)

A funcao percorre a arvore de erros (seguindo Unwrap() e Unwrap() []error) e retorna o primeiro erro que corresponde ao tipo E. Se nenhum for encontrado, retorna o valor zero de E e false.

Comparacao Direta: errors.As vs errors.AsType

Veja lado a lado como o codigo fica mais limpo:

// ANTES: errors.As (Go 1.13+)
var netErr *net.OpError
if errors.As(err, &netErr) {
	log.Printf("Operacao de rede falhou: %s", netErr.Op)
}

// DEPOIS: errors.AsType (Go 1.26+)
if netErr, ok := errors.AsType[*net.OpError](err); ok {
	log.Printf("Operacao de rede falhou: %s", netErr.Op)
}
// ANTES: multiplos tipos de erro
var syntaxErr *json.SyntaxError
var typeErr *json.UnmarshalTypeError

if errors.As(err, &syntaxErr) {
	log.Printf("JSON invalido na posicao %d", syntaxErr.Offset)
} else if errors.As(err, &typeErr) {
	log.Printf("Tipo incompativel: esperava %s, recebeu %s", typeErr.Type, typeErr.Value)
}

// DEPOIS: mais conciso e seguro
if syntaxErr, ok := errors.AsType[*json.SyntaxError](err); ok {
	log.Printf("JSON invalido na posicao %d", syntaxErr.Offset)
} else if typeErr, ok := errors.AsType[*json.UnmarshalTypeError](err); ok {
	log.Printf("Tipo incompativel: esperava %s, recebeu %s", typeErr.Type, typeErr.Value)
}

As variaveis ficam contidas no escopo do if, o compilador verifica os tipos em tempo de compilacao, e nao ha risco de panic por reflexao.

Exemplo Pratico: Erros Customizados em APIs

Em APIs REST e microsservicos, e comum definir tipos de erro que carregam informacoes extras como codigo HTTP e mensagens para o usuario:

type AppError struct {
	Code    int
	Message string
	Detail  string
	Err     error
}

func (e *AppError) Error() string {
	return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Detail)
}

func (e *AppError) Unwrap() error {
	return e.Err
}

type ValidationError struct {
	Field   string
	Value   any
	Message string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("validacao falhou no campo %s: %s", e.Field, e.Message)
}

Agora, no handler HTTP, usar errors.AsType torna o tratamento muito mais legivel:

func handleRequest(w http.ResponseWriter, r *http.Request) {
	resultado, err := processarPedido(r.Context(), r.Body)
	if err != nil {
		// Verifica erro de aplicacao
		if appErr, ok := errors.AsType[*AppError](err); ok {
			http.Error(w, appErr.Message, appErr.Code)
			slog.Error("erro na requisicao",
				"code", appErr.Code,
				"detail", appErr.Detail,
			)
			return
		}

		// Verifica erro de validacao
		if valErr, ok := errors.AsType[*ValidationError](err); ok {
			resp := map[string]string{
				"campo":    valErr.Field,
				"mensagem": valErr.Message,
			}
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusBadRequest)
			json.NewEncoder(w).Encode(resp)
			return
		}

		// Erro generico
		http.Error(w, "Erro interno", http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(resultado)
}

Usar slog para logging estruturado junto com errors.AsType cria um fluxo de tratamento de erros completo e rastreavel.

Performance: AsType vs As

errors.AsType nao e apenas mais seguro – e mais rapido. Como o tipo e resolvido em tempo de compilacao via generics, nao ha chamadas a reflect em runtime:

func BenchmarkErrorsAs(b *testing.B) {
	err := &fs.PathError{Op: "open", Path: "/tmp/teste", Err: os.ErrNotExist}
	wrapped := fmt.Errorf("falha: %w", err)

	b.Run("errors.As", func(b *testing.B) {
		for b.Loop() {
			var target *fs.PathError
			errors.As(wrapped, &target)
		}
	})

	b.Run("errors.AsType", func(b *testing.B) {
		for b.Loop() {
			errors.AsType[*fs.PathError](wrapped)
		}
	})
}

Resultados tipicos mostram que errors.AsType e entre 2x e 5x mais rapido que errors.As para tipos concretos, principalmente porque elimina a alocacao do ponteiro intermediario e as chamadas a reflexao.

Note o uso de b.Loop() – o novo padrao de benchmarks introduzido no Go 1.24 que substitui b.N e evita problemas com otimizacoes do compilador.

Migrando Codigo Existente com go fix

O Go 1.26 trouxe um go fix renovado que inclui modernizadores automaticos. Um deles converte chamadas errors.As para errors.AsType quando seguro:

# Analisa o projeto inteiro
go fix ./...

# Visualiza as mudancas sem aplicar
go fix -diff ./...

O modernizador e conservador: so converte quando tem certeza de que a transformacao preserva a semantica. Casos ambiguos sao deixados para revisao manual. Combine com testes automatizados para validar que o comportamento nao mudou apos a migracao.

Erros Wrapped e Arvores de Erros

errors.AsType percorre a mesma arvore que errors.As. Com o suporte a Unwrap() []error (introduzido no Go 1.20 com errors.Join), a busca e em profundidade:

func exemploJoin() {
	err1 := &AppError{Code: 404, Message: "nao encontrado"}
	err2 := &ValidationError{Field: "email", Message: "formato invalido"}

	// Join cria um erro com multiplos filhos
	combined := errors.Join(err1, err2)

	// AsType encontra ambos
	if appErr, ok := errors.AsType[*AppError](combined); ok {
		fmt.Println("AppError encontrado:", appErr.Code)
	}

	if valErr, ok := errors.AsType[*ValidationError](combined); ok {
		fmt.Println("ValidationError encontrado:", valErr.Field)
	}
}

Esse padrao e especialmente util em pipelines de concorrencia onde multiplas goroutines podem retornar erros diferentes, e voce precisa inspecionar cada tipo individualmente.

Padrao: Funcao Helper com AsType

Um padrao que emerge naturalmente com errors.AsType e criar funcoes helper que encapsulam a logica de mapeamento erro-para-resposta:

// errorToHTTPStatus mapeia erros para codigos HTTP
func errorToHTTPStatus(err error) (int, string) {
	if appErr, ok := errors.AsType[*AppError](err); ok {
		return appErr.Code, appErr.Message
	}

	if _, ok := errors.AsType[*ValidationError](err); ok {
		return http.StatusBadRequest, "Dados invalidos"
	}

	if pathErr, ok := errors.AsType[*fs.PathError](err); ok {
		if errors.Is(pathErr.Err, os.ErrNotExist) {
			return http.StatusNotFound, "Recurso nao encontrado"
		}
		if errors.Is(pathErr.Err, os.ErrPermission) {
			return http.StatusForbidden, "Acesso negado"
		}
	}

	return http.StatusInternalServerError, "Erro interno"
}

Esse helper centraliza o mapeamento e pode ser reutilizado em todos os handlers da aplicacao. Com clean architecture, essa funcao vive na camada de infraestrutura HTTP, separada da logica de negocio.

Outras Novidades do Go 1.26 Relacionadas

Alem de errors.AsType, o Go 1.26 trouxe outras melhorias que complementam o tratamento de erros:

new() com expressao – agora voce pode inicializar ponteiros inline:

// Antes
enabled := true
config := Config{Enabled: &enabled}

// Go 1.26
config := Config{Enabled: new(true)}

Generics auto-referenciantes – tipos genericos podem referenciar a si mesmos:

type Comparable[T Comparable[T]] interface {
	CompareTo(T) int
}

Essas mudancas, combinadas com o Green Tea GC habilitado por padrao e o go fix modernizado, tornam o Go 1.26 uma atualizacao que vale a migracao imediata. Se voce esta explorando as novidades do ecossistema, veja tambem como criar servidores MCP em Go para integrar suas aplicacoes com assistentes de IA.

Quando Usar errors.As vs errors.AsType

CenarioRecomendacao
Codigo novo (Go 1.26+)Sempre errors.AsType
Codigo legado em migracaoUse go fix para converter automaticamente
Bibliotecas com suporte a Go < 1.26Mantenha errors.As para compatibilidade
Hot paths com muitas verificacoes de tipoerrors.AsType (2-5x mais rapido)
Erros com Unwrap() []errorAmbos funcionam identicamente

FAQ

errors.AsType substitui errors.As completamente? Funcionalmente sim, mas errors.As nao foi deprecado. Para codigo que precisa suportar versoes anteriores ao Go 1.26, continue usando errors.As. Para codigo novo, prefira errors.AsType.

errors.AsType funciona com interfaces de erro? Sim. Voce pode usar errors.AsType[MinhaInterface](err) onde MinhaInterface e qualquer interface que implemente error. O compilador valida a constraint em tempo de compilacao.

Qual o impacto real de performance? Em benchmarks sinteticos, errors.AsType e 2-5x mais rapido. Em aplicacoes reais, o impacto depende de quantas verificacoes de tipo de erro voce faz por requisicao. Para APIs de alta performance, a diferenca pode ser mensuravel.

O go fix converte todos os errors.As automaticamente? Nao. O modernizador e conservador e so converte quando a transformacao e comprovadamente segura. Casos que envolvem interfaces ou tipos importados de pacotes externos podem precisar de migracao manual.

Posso usar errors.AsType em testes? Absolutamente. Na verdade, testes ficam mais limpos:

if appErr, ok := errors.AsType[*AppError](err); ok {
    assert.Equal(t, 404, appErr.Code)
}

Combine com table-driven tests e fuzzing para cobertura completa do tratamento de erros.