std.debug em Zig — Referência e Exemplos

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

  1. Use std.debug.print para depuração temporária — ele vai para stderr e nunca causa erro de compilação por resultado não tratado
  2. Use std.debug.assert para invariantes internas — são removidos em builds otimizados (ReleaseSafe, ReleaseFast)
  3. Nunca use std.debug.print para saída de produção — use std.io ou std.log para isso
  4. Prefira std.log para logging estruturado em produção
  5. 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

Tutoriais Relacionados

Continue aprendendo Zig

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