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:
- Verbosidade – voce precisa declarar a variavel
pathErrantes de usa-la - Reflexao em runtime –
errors.Asusareflectpara validar tipos, com custo de performance - Risco de panic – se voce passar um tipo errado (nao-ponteiro para interface), o programa entra em panic
- Escopo da variavel –
pathErrfica acessivel fora do blocoif, 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
| Cenario | Recomendacao |
|---|---|
| Codigo novo (Go 1.26+) | Sempre errors.AsType |
| Codigo legado em migracao | Use go fix para converter automaticamente |
| Bibliotecas com suporte a Go < 1.26 | Mantenha errors.As para compatibilidade |
| Hot paths com muitas verificacoes de tipo | errors.AsType (2-5x mais rapido) |
Erros com Unwrap() []error | Ambos 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.