std.debug — Depuração e Diagnóstico
O módulo std.debug contém utilitários de depuração para desenvolvimento em Zig. Inclui saída de diagnóstico via stderr, assertions, stack traces e ferramentas de introspecção. Diferente de std.io, as funções de std.debug são projetadas para desenvolvimento e diagnóstico, não para saída de produção.
Visão Geral
const std = @import("std");
const debug = std.debug;
A principal vantagem de std.debug.print sobre std.io é que ele nunca retorna erros — a saída de debug é fire-and-forget. Isso o torna ideal para depuração rápida sem precisar lidar com try ou catch.
Funções Principais
// Imprime formatado no stderr (nunca falha)
pub fn print(comptime fmt: []const u8, args: anytype) void
// Assertion — aborta se a condição for falsa
pub fn assert(ok: bool) void
// Stack trace do ponto atual
pub fn dumpCurrentStackTrace() void
// Captura o stack trace atual
pub fn captureStackTrace() StackTrace
// Informações sobre o endereço de retorno
pub fn getReturnAddress() usize
Exemplo 1: Print de Depuração
const std = @import("std");
const Ponto = struct {
x: f64,
y: f64,
pub fn distancia(self: Ponto, outro: Ponto) f64 {
const dx = self.x - outro.x;
const dy = self.y - outro.y;
return @sqrt(dx * dx + dy * dy);
}
};
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
// std.debug.print vai para stderr (não mistura com stdout)
std.debug.print("Iniciando programa...\n", .{});
const p1 = Ponto{ .x = 3.0, .y = 4.0 };
const p2 = Ponto{ .x = 7.0, .y = 1.0 };
// Debug print aceita qualquer tipo formatável
std.debug.print("p1 = {}\n", .{p1});
std.debug.print("p2 = {}\n", .{p2});
const dist = p1.distancia(p2);
std.debug.print("Distância calculada: {d:.4}\n", .{dist});
// Saída normal vai para stdout
try stdout.print("Distância entre pontos: {d:.2}\n", .{dist});
// Debug com slices e arrays
const numeros = [_]i32{ 10, 20, 30, 40, 50 };
std.debug.print("Array: {any}\n", .{numeros});
const nome: []const u8 = "Zig Brasil";
std.debug.print("Nome: {s} (len={d})\n", .{ nome, nome.len });
// Debug com ponteiros e optionals
var valor: i32 = 42;
const ptr = &valor;
const opt: ?i32 = 99;
const opt_null: ?i32 = null;
std.debug.print("Ponteiro: {*}, valor={d}\n", .{ ptr, ptr.* });
std.debug.print("Optional com valor: {?d}\n", .{opt});
std.debug.print("Optional null: {?d}\n", .{opt_null});
}
Exemplo 2: Assertions e Validação
const std = @import("std");
/// Pilha genérica com assertions de segurança.
fn PilhaSegura(comptime T: type, comptime capacidade_max: usize) type {
return struct {
const Self = @This();
dados: [capacidade_max]T = undefined,
topo: usize = 0,
pub fn empilhar(self: *Self, item: T) void {
// Assert: não excede a capacidade
std.debug.assert(self.topo < capacidade_max);
self.dados[self.topo] = item;
self.topo += 1;
}
pub fn desempilhar(self: *Self) T {
// Assert: pilha não está vazia
std.debug.assert(self.topo > 0);
self.topo -= 1;
return self.dados[self.topo];
}
pub fn espiar(self: *const Self) T {
std.debug.assert(self.topo > 0);
return self.dados[self.topo - 1];
}
pub fn estaVazia(self: *const Self) bool {
return self.topo == 0;
}
pub fn tamanho(self: *const Self) usize {
return self.topo;
}
};
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
var pilha = PilhaSegura(i32, 10){};
pilha.empilhar(10);
pilha.empilhar(20);
pilha.empilhar(30);
try stdout.print("Topo: {d}\n", .{pilha.espiar()});
try stdout.print("Tamanho: {d}\n", .{pilha.tamanho()});
const removido = pilha.desempilhar();
try stdout.print("Removido: {d}\n", .{removido});
// Demonstração de assert em cálculos
const a: i32 = 10;
const b: i32 = 3;
const quociente = @divTrunc(a, b);
const resto = @rem(a, b);
// Invariante: a == b * quociente + resto
std.debug.assert(a == b * quociente + resto);
try stdout.print("{d} = {d} * {d} + {d} ✓\n", .{ a, b, quociente, resto });
}
Exemplo 3: Stack Traces
const std = @import("std");
fn nivel3() void {
std.debug.print("=== Stack Trace no nível 3 ===\n", .{});
std.debug.dumpCurrentStackTrace();
}
fn nivel2() void {
std.debug.print("Chamando nível 3...\n", .{});
nivel3();
}
fn nivel1() void {
std.debug.print("Chamando nível 2...\n", .{});
nivel2();
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.writeAll("=== Demonstração de Stack Trace ===\n\n");
// Chama cadeia de funções para gerar stack trace
nivel1();
try stdout.writeAll("\n=== Endereço de Retorno ===\n");
const ret_addr = @returnAddress();
try stdout.print("Endereço de retorno de main: 0x{x}\n", .{ret_addr});
}
Exemplo 4: Debug Condicional com comptime
const std = @import("std");
const MODO_DEBUG = @import("builtin").mode == .Debug;
fn logDebug(comptime fmt: []const u8, args: anytype) void {
if (MODO_DEBUG) {
std.debug.print("[DEBUG] " ++ fmt ++ "\n", .{} ++ args);
}
}
/// Busca binária com logging de depuração.
fn buscaBinaria(arr: []const i32, alvo: i32) ?usize {
var esq: usize = 0;
var dir: usize = arr.len;
var iteracao: u32 = 0;
while (esq < dir) {
const meio = esq + (dir - esq) / 2;
iteracao += 1;
if (MODO_DEBUG) {
std.debug.print(" iteração {d}: esq={d}, dir={d}, meio={d}, arr[meio]={d}\n", .{
iteracao, esq, dir, meio, arr[meio],
});
}
if (arr[meio] == alvo) {
if (MODO_DEBUG) {
std.debug.print(" Encontrado na posição {d} após {d} iterações\n", .{ meio, iteracao });
}
return meio;
} else if (arr[meio] < alvo) {
esq = meio + 1;
} else {
dir = meio;
}
}
if (MODO_DEBUG) {
std.debug.print(" Não encontrado após {d} iterações\n", .{iteracao});
}
return null;
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const dados = [_]i32{ 2, 5, 8, 12, 16, 23, 38, 56, 72, 91 };
std.debug.print("Buscando 23 em {any}:\n", .{dados});
if (buscaBinaria(&dados, 23)) |pos| {
try stdout.print("Valor 23 encontrado na posição {d}\n", .{pos});
}
std.debug.print("\nBuscando 50:\n", .{});
if (buscaBinaria(&dados, 50)) |pos| {
try stdout.print("Valor 50 encontrado na posição {d}\n", .{pos});
} else {
try stdout.writeAll("Valor 50 não encontrado\n");
}
}
Boas Práticas
- Use
std.debug.printpara depuração temporária — ele vai para stderr e nunca causa erro de compilação por resultado não tratado - Use
std.debug.assertpara invariantes internas — são removidos em builds otimizados (ReleaseSafe,ReleaseFast) - Nunca use
std.debug.printpara saída de produção — usestd.iooustd.logpara isso - Prefira
std.logpara logging estruturado em produção - Stack traces são valiosos em desenvolvimento, mas podem expor detalhes em produção
Módulos Relacionados
- std.log — Logging estruturado para produção
- std.testing — Framework de testes
- std.io — Entrada e saída padrão
- std.process — Gerenciamento de processos
- std.os — Interface com o sistema operacional