Attempt to Unwrap Error — Como Resolver em Zig

Attempt to Unwrap Error — Como Resolver em Zig

O Que Este Erro Significa

O panic attempt to unwrap error ocorre em runtime quando você tenta extrair o valor de sucesso de um error union (!T) que na verdade contém um erro. Isso acontece ao usar o operador .? ou catch unreachable em um error union que falhou. O programa assume que a operação teve sucesso, mas na verdade um erro ocorreu.

A mensagem de panic:

thread 1 panic: attempt to unwrap erroneous value: error.NomeDoErro

Este panic indica que o programador não tratou adequadamente a possibilidade de falha da operação.

Causas Comuns

1. Usar .? em Error Union com Erro

const std = @import("std");

fn obterValor(sucesso: bool) !u32 {
    if (!sucesso) return error.Falha;
    return 42;
}

pub fn main() void {
    const resultado: !u32 = obterValor(false);
    const valor = resultado catch unreachable;
    // PANIC: attempt to unwrap erroneous value: error.Falha
    _ = valor;
}

2. try em Função Chamada que Falha

const std = @import("std");

fn conectar(endereco: []const u8) !void {
    _ = endereco;
    return error.ConexaoRecusada;
}

pub fn main() !void {
    // PANIC propagado como erro: error.ConexaoRecusada
    try conectar("localhost:8080");
}

3. Desempacotar Resultado de Busca que Não Encontrou

const std = @import("std");

const BuscaError = error{NaoEncontrado};

fn buscarUsuario(id: u32) BuscaError![]const u8 {
    if (id != 1) return error.NaoEncontrado;
    return "admin";
}

pub fn main() void {
    // PANIC: usuário 99 não existe
    const nome = buscarUsuario(99) catch unreachable;
    _ = nome;
}

4. Encadeamento de Operações Falháveis

const std = @import("std");

fn lerArquivo() ![]const u8 {
    return error.FileNotFound;
}

fn parsearConfig(dados: []const u8) !void {
    _ = dados;
}

pub fn main() void {
    const dados = lerArquivo() catch unreachable; // PANIC aqui
    parsearConfig(dados) catch unreachable;
}

5. Alocação em Contexto sem Tratamento

const std = @import("std");

pub fn main() void {
    var buf: [16]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buf);
    const allocator = fba.allocator();

    // Pede mais do que o disponível
    const dados = allocator.alloc(u8, 1024) catch unreachable;
    // PANIC: attempt to unwrap erroneous value: error.OutOfMemory
    _ = dados;
}

Como Corrigir

Solucao 1: Usar try para Propagar

const std = @import("std");

fn conectar(endereco: []const u8) !void {
    _ = endereco;
    return error.ConexaoRecusada;
}

pub fn main() !void {
    try conectar("localhost:8080");
    // Se falhar, o erro é retornado de main
    std.debug.print("Conectado!\n", .{});
}

Solucao 2: Tratar com catch e Bloco

const std = @import("std");

fn buscarUsuario(id: u32) ![]const u8 {
    if (id != 1) return error.NaoEncontrado;
    return "admin";
}

pub fn main() void {
    const nome = buscarUsuario(99) catch |err| {
        std.debug.print("Usuário não encontrado: {}\n", .{err});
        return;
    };
    std.debug.print("Bem-vindo, {s}!\n", .{nome});
}

Solucao 3: Usar catch com Valor Padrão

const std = @import("std");

fn buscarUsuario(id: u32) ![]const u8 {
    if (id != 1) return error.NaoEncontrado;
    return "admin";
}

pub fn main() void {
    const nome = buscarUsuario(99) catch "visitante";
    std.debug.print("Bem-vindo, {s}!\n", .{nome});
}

Solucao 4: Usar switch no Erro

const std = @import("std");

const DbError = error{ NaoEncontrado, ConexaoPerdida, PermissaoNegada };

fn buscarRegistro(id: u32) DbError![]const u8 {
    _ = id;
    return error.NaoEncontrado;
}

pub fn main() void {
    const dados = buscarRegistro(42) catch |err| switch (err) {
        error.NaoEncontrado => {
            std.debug.print("Registro não existe\n", .{});
            return;
        },
        error.ConexaoPerdida => {
            std.debug.print("Banco de dados indisponível\n", .{});
            return;
        },
        error.PermissaoNegada => {
            std.debug.print("Sem permissão\n", .{});
            return;
        },
    };
    std.debug.print("Dados: {s}\n", .{dados});
}

Solucao 5: if com Error Union

const std = @import("std");

fn dividir(a: u32, b: u32) !u32 {
    if (b == 0) return error.DivisaoPorZero;
    return a / b;
}

pub fn main() void {
    if (dividir(10, 0)) |resultado| {
        std.debug.print("Resultado: {}\n", .{resultado});
    } else |err| {
        std.debug.print("Erro na divisão: {}\n", .{err});
    }
}

Solucao 6: Retry com Limite

const std = @import("std");

fn conectar(endereco: []const u8) !void {
    _ = endereco;
    return error.ConexaoRecusada;
}

fn conectarComRetry(endereco: []const u8, max_tentativas: u32) !void {
    var tentativa: u32 = 0;
    while (tentativa < max_tentativas) : (tentativa += 1) {
        conectar(endereco) catch |err| {
            std.debug.print("Tentativa {}: {}\n", .{ tentativa + 1, err });
            std.time.sleep(1_000_000_000); // 1 segundo
            continue;
        };
        return; // Sucesso
    }
    return error.ConexaoRecusada;
}

Diferenca: Unwrap Error vs Unwrap Null

// Error Union (!T) — usa catch
const valor_error: !u32 = error.Falha;
const v1 = valor_error catch 0; // Trata o erro

// Optional (?T) — usa orelse
const valor_opt: ?u32 = null;
const v2 = valor_opt orelse 0; // Trata o null

Ambos podem causar panic se usados com unreachable, mas são tipos diferentes com operadores diferentes.

Quando catch unreachable É Justificado

Somente quando você tem prova lógica de que o erro não pode ocorrer:

const std = @import("std");

fn exemploSeguro() void {
    // std.fmt.parseInt com string literal conhecida
    const numero = std.fmt.parseInt(u32, "42", 10) catch unreachable;
    // "42" é SEMPRE um u32 válido — catch unreachable é seguro
    _ = numero;
}

Erros Relacionados

Continue aprendendo Zig

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