Error Sets Customizados em Zig

Introdução

Error sets em Zig permitem definir conjuntos específicos de erros que uma função pode retornar. Isso dá ao chamador informação precisa sobre quais erros esperar, sem a necessidade de hierarquias de exceções como em Java ou C#.

Para padrões de uso com try/catch, veja Padrões Try/Catch. Para cleanup, consulte Padrões Errdefer.

Pré-requisitos

Definir Error Sets

Error Set Simples

const ArquivoError = error{
    NaoEncontrado,
    SemPermissao,
    Corrompido,
    DiskCheio,
};

fn abrirArquivo(caminho: []const u8) ArquivoError!std.fs.File {
    _ = caminho;
    return error.NaoEncontrado;
}

Error Set com Documentação

const ParseError = error{
    /// Token inesperado encontrado durante parsing
    TokenInesperado,
    /// Fim de input antes do esperado
    FimPrematuro,
    /// Valor numérico fora do intervalo válido
    ValorForaDoIntervalo,
    /// Encoding de caractere inválido
    EncodingInvalido,
};

Composição de Error Sets

Unir Error Sets

const IoError = error{
    ArquivoNaoEncontrado,
    PermissaoNegada,
    Timeout,
};

const ParseError = error{
    SintaxeInvalida,
    TokenInesperado,
};

// Combinar dois error sets
const ConfigError = IoError || ParseError;

fn carregarConfig(caminho: []const u8) ConfigError!Config {
    const dados = lerArquivo(caminho) catch |err| return err;
    return parsear(dados) catch |err| return err;
}

Error Set Implícito com !

// ! sem tipo explícito: error set inferido pelo compilador
fn processar(dados: []const u8) !Resultado {
    // O compilador infere automaticamente quais erros são possíveis
    const arquivo = try std.fs.cwd().openFile(dados, .{});
    defer arquivo.close();
    // ...
}

Usar Error Sets em Switch

const DbError = error{
    ConexaoRecusada,
    QueryInvalida,
    RegistroNaoEncontrado,
    ConstraintViolada,
    Timeout,
};

fn executarQuery(query: []const u8) DbError!Resultado {
    _ = query;
    return error.Timeout;
}

fn tratarErroDb(err: DbError) void {
    switch (err) {
        error.ConexaoRecusada => {
            std.debug.print("Servidor não disponível. Tente novamente.\n", .{});
        },
        error.QueryInvalida => {
            std.debug.print("Query SQL inválida.\n", .{});
        },
        error.RegistroNaoEncontrado => {
            std.debug.print("Registro não encontrado.\n", .{});
        },
        error.ConstraintViolada => {
            std.debug.print("Violação de constraint.\n", .{});
        },
        error.Timeout => {
            std.debug.print("Operação expirou.\n", .{});
        },
    }
}

Error Sets em Structs

const HttpClient = struct {
    pub const Error = error{
        ConexaoRecusada,
        Timeout,
        SslHandshakeFailed,
        RespostaInvalida,
        RedirectLoop,
    };

    pub fn get(self: *HttpClient, url: []const u8) Error!Resposta {
        _ = self;
        _ = url;
        return error.Timeout;
    }

    pub fn post(self: *HttpClient, url: []const u8, body: []const u8) Error!Resposta {
        _ = self;
        _ = url;
        _ = body;
        return error.ConexaoRecusada;
    }
};

Converter entre Error Sets

const MeuErro = error{
    Generico,
    Especifico,
};

fn mapearErro(err: anyerror) MeuErro {
    return switch (err) {
        error.FileNotFound => error.Especifico,
        error.AccessDenied => error.Especifico,
        else => error.Generico,
    };
}

Error Set como Valor

const std = @import("std");

fn logErro(err: anyerror) void {
    std.debug.print("Erro: {s}\n", .{@errorName(err)});
}

fn transformarErro(err: anyerror) !void {
    std.debug.print("Erro original: {s}\n", .{@errorName(err)});
    return err;
}

Boas Práticas

1. Nomeie erros descritivamente

// BOM
const Error = error{BufferCheio, ConexaoInterrompida, FormatoInvalido};

// RUIM
const Error = error{Err1, Err2, Err3};

2. Use error sets específicos em APIs públicas

// BOM: chamador sabe exatamente o que pode acontecer
pub fn conectar(host: []const u8) error{DnsNaoResolvido, ConexaoRecusada, Timeout}!Conexao {
    // ...
}

// Aceitável para funções internas: ! inferido
fn processarInterno(dados: []const u8) !Resultado {
    // ...
}

3. Componha error sets para funções que chamam múltiplas sub-funções

pub const ProcessarError = IoError || ParseError || ValidacaoError;

Testes

const std = @import("std");

test "erro específico" {
    const resultado = abrirArquivo("inexistente.txt");
    try std.testing.expectError(error.NaoEncontrado, resultado);
}

test "sucesso" {
    const resultado = try abrirArquivo("existente.txt");
    _ = resultado;
}

Veja Testes Unitários e Test Expectations.

Conclusão

Error sets em Zig são tipados, composíveis e verificados pelo compilador. Eles oferecem toda a informação necessária para tratamento de erros sem a complexidade de hierarquias de exceções.

Para padrões relacionados, veja Padrões Try/Catch, Padrões Errdefer e Error Logging.

Continue aprendendo Zig

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