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
- Null unwrap — Desempacotar optional null
- Error not handled — Erro não tratado
- Catch reached unreachable — Catch atingiu unreachable
- Try in non-error function — Try em função sem retorno de erro