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:
- Importa el módulo estándar
std. - Define la función
calcularque recibirá dos enteros y devolverá su suma. - Invoca
@src()y lo asigna a la constantesrc. Esta llamada es evaluada en tiempo de compilación. - Se utiliza
std.debug.printpara escribir un mensaje en la salida estándar. Los marcadores {s} y {d} corresponden a string y decimal respectivamente. - 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 archivolib.zig.src.line– número de línea donde se colocó@src().
- Se devuelve la suma de
ayb.
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
