¿Cómo usar @src().fn_name?

¿Cómo usar @src().fn_name?




Uso avanzado de @src().fn_name en Zig


El sistema de introspección de Zig permite extraer información sobre el punto de origen del código durante la compilación. Entre los atributos disponibles, @src() devuelve un SourceLocation que incluye la ruta del archivo, la línea, la columna y el nombre de la función actual. En este artículo exploraremos cómo aprovechar @src().fn_name para mejorar el depurado, la generación de mensajes de error y la documentación dinámica de tu código.

Qué es @src() y por qué importa fn_name

El macro @src() es evaluado en tiempo de compilación y devuelve una estructura de la siguiente forma:

pub const SourceLocation = struct {
    file: []const u8,   // ruta relativa al archivo
    line: u32,          // número de línea
    column: u32,        // número de columna
    fn_name: []const u8 // nombre de la función donde se invoca @src()
};

La propiedad fn_name contiene el nombre de la función en la que se llama al macro, lo que facilita la creación de trazas y mensajes contextuales sin escribir manualmente cadenas literales.

Ventajas de usar fn_name

  • Reducción de código duplicado: evita tener que escribir el nombre de la función en cada mensaje.
  • Menor riesgo de errores tipográficos: el compilador valida que el nombre exista.
  • Actualizaciones automáticas: al renombrar la función, los mensajes se actualizan automáticamente.

Instalación y configuración básica

Para usar @src() no necesitas ninguna dependencia adicional; basta con incluir el módulo estándar std.debug y el compilador Zig 0.12.0 o superior.

const std = @import("std");

Estrategias comunes de uso

1. Mensajes de depuración con std.debug.print

El siguiente fragmento muestra cómo imprimir información de la función actual sin escribir su nombre manualmente:

// archivo: lib.zig
const std = @import("std");

// Función de ejemplo donde se desea depurar
pub fn calcular(a: i32, b: i32) i32 {
    // Obtiene la ubicación de origen en tiempo de compilación
    const src = @src();

    // Imprime el nombre de la función, el archivo y la línea
    std.debug.print(
        \\[DEBUG] Función: {s} | Archivo: {s} | Línea: {d}\n,
        .{ src.fn_name, src.file, src.line }
    );

    return a + b;
}

Explicación línea por línea:

  1. Importa el módulo estándar std.
  2. Define la función calcular que recibirá dos enteros y devolverá su suma.
  3. Invoca @src() y lo asigna a la constante src. Esta llamada es evaluada en tiempo de compilación.
  4. Se utiliza std.debug.print para escribir un mensaje en la salida estándar. Los marcadores {s} y {d} corresponden a string y decimal respectivamente.
  5. El array de parámetros pasados a la función de impresión contiene:
    • src.fn_name – nombre de la función donde se llamó @src().
    • src.file – ruta del archivo lib.zig.
    • src.line – número de línea donde se colocó @src().
  6. Se devuelve la suma de a y b.

2. Creación de excepciones personalizadas con contexto de función

Para lanzar errores que incluyan automáticamente el nombre de la función, puedes definir un helper:

// archivo: error_context.zig
const std = @import("std");

pub fn errorWithContext(msg: []const u8) !void {
    const src = @src(); // Captura la ubicación
    std.debug.print(
        \\[ERROR] {s} en {s}:{d}\n,
        .{ msg, src.fn_name, src.line }
    );
    return error.Unknown; // Devuelve un error genérico
}

Uso en otra función:

pub fn procesarDatos(datos: []i32) !void {
    // Si algo falla, lanzar error con contexto automáticamente
    if (datos.len == 0) return errorWithContext("Datos vacíos");
    // Resto de lógica...
}

Al llamar errorWithContext desde procesarDatos, el mensaje mostrará “Datos vacíos en procesarDatos:23”, donde 23 es la línea de la llamada.

3. Registro de trazas (tracing) para sistemas embebidos

En dispositivos con recursos limitados, es útil generar trazas compactas que incluyan el nombre de la función sin usar cadenas estáticas largas. Un enfoque es convertir el nombre a un identificador numérico mediante hashing o un lookup de tabla.

// archivo: trace.zig
const std = @import("std");

fn trace(msg: []const u8) void {
    const src = @src();
    // Calcula un hash simple del nombre de la función para reducir el tamaño
    const hash = std.hash.Crc32.hash(src.fn_name, .{});
    // Imprime el hash y el mensaje
    std.debug.print("TRACE 0x{x}: {s}\n", .{ hash, msg });
}

// Ejemplo de función con traza
pub fn actualizar() void {
    trace("Iniciando actualización");
    // Código...
}

En tiempo de compilación, el valor de hash queda incrustado, lo que permite buscar el nombre en una tabla de símbolos en tiempo de ejecución sin almacenar la cadena completa.

4. Generación automática de documentación con fn_name

Si utilizas std.testing.refAllDecls o herramientas similares para generar documentación, puedes incluir el nombre de la función en los comentarios generados.

/// Documentación automática de la función
/// \n Función: {s}
/// \n Archivo: {s}
/// \n Línea: {d}
pub fn autoDoc(a: i32) i32 {
    const src = @src();
    // Lógica real de la función
    return a * 2;
}

Al compilar, los comentarios se expandirán con el nombre correcto

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 *