¿Cómo usar @tagName para depuración?

¿Cómo usar @tagName para depuración?

En Zig, la función @tagName es una herramienta de tiempo de compilación que devuelve el nombre textual de una etiqueta de un valor enum. Aunque su uso más evidente suele ser en la generación de mensajes de error o en la introspección de tipos, se vuelve particularmente útil cuando se necesita depurar código sin introducir dependencias externas o sin generar salidas excesivas.

Comprendiendo @tagName

La sintaxis básica es muy sencilla:

const color = Color.Red;
const name = @tagName(color); // "Red"

Observa cómo se puede obtener una representación legible del enum en tiempo de compilación, lo que permite que el compilador lo incruste directamente en los mensajes o en las estructuras de datos que se impriman en tiempo de ejecución.

Ventajas de usar @tagName para depuración

  • Sin sobrecarga en tiempo de ejecución: La conversión ocurre en tiempo de compilación, por lo que no hay coste de CPU ni memoria adicional.
  • Mensajes claros: Los logs incluyen nombres legibles en lugar de valores numéricos.
  • Portabilidad: No requiere librerías externas; Zig lo provee de forma nativa.

Ejemplo 1: Imprimir enums con @tagName

const std = @import("std");

// Definición de un enum simple
const Mood = enum {
    Happy,
    Sad,
    Angry,
};

pub fn main() !void {
    // Creamos una variable de tipo Mood
    const currentMood = Mood.Sad;

    // Usamos std.debug.print para mostrar el enum con su nombre
    // std.debug.print utiliza el formateo similar a printf
    std.debug.print("El estado actual es: {s}\n", .{ @tagName(currentMood) });

    // Alternativamente, si queremos un log persistente:
    const log = std.log.scoped(.app);
    log.info("Estado: {s}", .{ @tagName(currentMood) });
}

Desglose línea por línea:

  1. const std = @import("std"); – Importa el paquete estándar.
  2. Definimos el enum Mood con tres posibles valores.
  3. En main, inicializamos currentMood con Mood.Sad.
  4. El llamado a std.debug.print imprime el nombre del enum gracias a @tagName.
  5. Se muestra también cómo usar el sistema de logs de Zig para información persistente.

Ejemplo 2: Depuración de structs con campos enum

const std = @import("std");

const Status = enum {
    Ok,
    Warning,
    Error,
};

const Response = struct {
    code: i32,
    message: []const u8,
    status: Status,
};

pub fn main() !void {
    const res = Response{
        .code = 404,
        .message = "Not Found",
        .status = Status.Error,
    };

    // Imprimimos cada campo, convirtiendo el enum a nombre legible
    std.debug.print(
        \\Respuesta recibida:
        \\  Código: {d}
        \\  Mensaje: {s}
        \\  Estado: {s}
        \\
        , .{
            res.code,
            res.message,
            @tagName(res.status), // Convierte Status.Error a "Error"
        });
}

Notas explicativas:

  • El struct Response contiene un campo enum status.
  • La cadena formateada dentro de std.debug.print utiliza \\ para saltos de línea.
  • Se emplea @tagName(res.status) para mostrar el nombre del estado.

Ejemplo 3: Uso en un switch con depuración condicional

const std = @import("std");

const Action = enum {
    Start,
    Stop,
    Pause,
};

pub fn main() !void {
    const act = Action.Pause;

    // Switch que ejecuta diferentes bloques y muestra el nombre
    switch (act) {
        .Start => std.debug.print("Acción: {s}\n", .{ @tagName(act) }),
        .Stop => std.debug.print("Acción: {s}\n", .{ @tagName(act) }),
        .Pause => std.debug.print("Acción: {s}\n", .{ @tagName(act) }),
    }
}

En este ejemplo, aunque cada rama del switch haga lo mismo, se ilustra cómo @tagName puede sustituir la necesidad de escribir manualmente los nombres de las etiquetas.

Ejemplo 4: Generar mensajes de error personalizados con @tagName

Cuando se necesita lanzar un error con información de un enum, @tagName permite crear mensajes más comprensibles:

const std = @import("std");

const FileError = error {
    NotFound,
    PermissionDenied,
    Unknown,
};

const FileState = enum {
    Closed,
    Open,
    Locked,
};

fn openFile(state: FileState) FileError!void {
    // Simulamos un escenario donde el archivo debe estar abierto
    if (state != FileState.Open) {
        // Usamos @tagName para incluir el nombre del estado en el error
        return error.NotFound; // En un caso real, se usaría un error personalizado
    }
    // Código para abrir el archivo...
}

pub fn main() !void {
    // Intentamos abrir con estado cerrado
    const res = openFile(FileState.Closed);
    switch (res) {
        error.NotFound => std.debug.print(
            \\Error: {s} - El archivo no está en estado {s}
            \\
            , .{ @tagName(res), @tagName(FileState.Closed) }),
        else => {},
    }
}

Comentario de línea a línea:

  1. Se define un error enum FileError con varios casos.
  2. El enum FileState modela el estado del archivo.
  3. La función openFile comprueba si el estado es Open; si no, devuelve NotFound.
  4. En main, se intenta abrir con un estado incorrecto.
  5. El

Comments

No comments yet. Why don’t you start the discussion?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *