@errorName em Zig — Referência e Exemplos

@errorName em Zig

O @errorName retorna o nome de um valor de erro como uma string legível. É uma ferramenta essencial para depuração, logging e mensagens de erro amigáveis. Em Zig, erros são valores de primeira classe, e este builtin permite converter esses valores para suas representações textuais.

Sintaxe

@errorName(err: anyerror) [:0]const u8

O que faz

O @errorName recebe um valor de erro e retorna seu nome como uma string de bytes constante terminada em sentinela nulo. O nome retornado corresponde exatamente ao identificador usado na declaração do error set.

Parâmetros

  • err (anyerror): Um valor de erro de qualquer error set. Deve ser um erro concreto, não um error union (!T).

Valor de retorno

Retorna [:0]const u8 — uma string terminada em zero contendo o nome do erro.

Exemplos práticos

Exemplo 1: Logging de erros

const std = @import("std");

const ArquivoErro = error{
    NaoEncontrado,
    PermissaoNegada,
    DiskCheio,
    Corrompido,
};

fn abrirArquivo(caminho: []const u8) ArquivoErro!void {
    _ = caminho;
    return ArquivoErro.NaoEncontrado;
}

test "logging de erros" {
    const resultado = abrirArquivo("/tmp/teste.txt");
    if (resultado) |_| {
        // Sucesso
    } else |err| {
        const nome = @errorName(err);
        std.debug.print("Erro ao abrir arquivo: {s}\n", .{nome});
        // Saída: "Erro ao abrir arquivo: NaoEncontrado"
        try std.testing.expectEqualStrings("NaoEncontrado", nome);
    }
}

Exemplo 2: Sistema de logging estruturado

const std = @import("std");

const NivelLog = enum { debug, info, warn, err };

fn log(nivel: NivelLog, mensagem: []const u8, erro_opt: ?anyerror) void {
    const prefixo = switch (nivel) {
        .debug => "[DEBUG]",
        .info => "[INFO]",
        .warn => "[WARN]",
        .err => "[ERRO]",
    };

    if (erro_opt) |err| {
        std.debug.print("{s} {s} (erro: {s})\n", .{
            prefixo,
            mensagem,
            @errorName(err),
        });
    } else {
        std.debug.print("{s} {s}\n", .{ prefixo, mensagem });
    }
}

test "logging estruturado" {
    log(.info, "Servidor iniciado", null);
    log(.err, "Falha na conexão", error.ConnectionRefused);
    log(.warn, "Tentativa de acesso negada", error.PermissaoNegada);
}

Exemplo 3: Tradução de erros para mensagens amigáveis

const std = @import("std");

const DbErro = error{
    ConexaoPerdida,
    TabelaNaoExiste,
    ChaveDuplicada,
    TimeoutConsulta,
};

fn mensagemAmigavel(err: DbErro) []const u8 {
    return switch (err) {
        error.ConexaoPerdida => "A conexão com o banco de dados foi perdida. Tente novamente.",
        error.TabelaNaoExiste => "A tabela solicitada não foi encontrada no banco de dados.",
        error.ChaveDuplicada => "Já existe um registro com essa chave. Use um valor diferente.",
        error.TimeoutConsulta => "A consulta demorou demais. Tente uma consulta mais simples.",
    };
}

fn tratarErroDb(err: DbErro) void {
    // Mensagem técnica para logs
    std.debug.print("DB Error: {s}\n", .{@errorName(err)});

    // Mensagem amigável para o usuário
    std.debug.print("Mensagem: {s}\n", .{mensagemAmigavel(err)});
}

test "mensagens de erro" {
    tratarErroDb(error.ConexaoPerdida);
    tratarErroDb(error.ChaveDuplicada);
}

Casos de uso comuns

  1. Logging e monitoramento: Registrar o nome do erro em logs para diagnóstico e análise de incidentes.
  2. Mensagens de erro para o usuário: Exibir o nome técnico do erro junto com uma mensagem amigável.
  3. Serialização de erros: Converter erros para strings ao transmitir via rede (APIs, RPC).
  4. Depuração: Imprimir erros durante o desenvolvimento para entender fluxos de execução.
  5. Telemetria: Enviar nomes de erros para sistemas de monitoramento e alertas.

Observações

  • O @errorName funciona com qualquer error set, incluindo anyerror.
  • A string retornada é estática e válida durante toda a execução do programa.
  • Não há operação inversa direta (string para erro) disponível como builtin. Para isso, seria necessário implementar manualmente usando @typeInfo.

Comparação com C equivalente

Em C, não existe um mecanismo nativo equivalente. Desenvolvedores costumam usar strerror() para erros do sistema ou criar tabelas manuais:

// C — tabela manual de mensagens de erro
const char* meu_strerror(int codigo) {
    switch (codigo) {
        case ERR_NAO_ENCONTRADO: return "NaoEncontrado";
        case ERR_PERMISSAO:      return "PermissaoNegada";
        default:                 return "Desconhecido";
    }
}

Em Zig, o compilador mantém automaticamente os nomes de todos os erros declarados, e @errorName os recupera sem nenhuma tabela manual:

// Zig — automático, sem manutenção
const nome = @errorName(err); // sempre correto, sempre atualizado

Isso elimina a possibilidade de a tabela ficar desatualizada quando novos erros são adicionados.

Detalhes técnicos

Os nomes retornados por @errorName são armazenados em uma seção de dados somente-leitura do binário gerado. O compilador coleta todos os valores de erro usados no programa e cria uma tabela de strings em tempo de compilação. Por isso, a chamada a @errorName em runtime é basicamente uma busca por índice em uma tabela — extremamente eficiente.

O tipo de retorno [:0]const u8 indica uma slice de bytes com sentinela nulo no final, o que a torna compatível com APIs C que esperam const char*. Isso é conveniente ao integrar logs Zig com sistemas de logging C.

Erros comuns

Erro: Tentar chamar @errorName em um error union sem primeiro capturar o erro.

// ERRADO — resultado é !void, não anyerror
const resultado = funcaoQueRetornaErro();
// @errorName(resultado) — não compila!

// CORRETO — capturar o erro primeiro
if (resultado) |_| {} else |err| {
    std.debug.print("{s}\n", .{@errorName(err)});
}

Perguntas Frequentes

P: O @errorName tem custo em performance?

É extremamente eficiente. O nome já está armazenado como string estática no binário. A chamada em runtime é essencialmente uma leitura de ponteiro de uma tabela gerada pelo compilador.

P: Como converter um nome de string de volta para um valor de erro?

Não existe builtin para isso. É preciso iterar sobre os erros do error set usando @typeInfo e comparar as strings manualmente. Para casos simples, um switch em string é mais direto:

fn stringParaErro(nome: []const u8) ?anyerror {
    if (std.mem.eql(u8, nome, "NaoEncontrado")) return error.NaoEncontrado;
    if (std.mem.eql(u8, nome, "PermissaoNegada")) return error.PermissaoNegada;
    return null;
}

P: @errorName funciona com anyerror?

Sim. O anyerror é o supertipo de todos os erros em Zig, e @errorName funciona com qualquer valor de erro, independentemente do error set específico.

Builtins relacionados

  • @tagName — Obtém o nome do campo ativo de uma tagged union
  • @typeName — Obtém o nome de um tipo como string
  • @typeInfo — Inspeciona error sets e outros tipos
  • @compileError — Gera erro de compilação personalizado

Tutoriais relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.