Cuando trabajas con el lenguaje Zig, el manejo de strings y la creación de salidas formateadas es fundamental para producir mensajes claros y bien estructurados. La función std.fmt.format de la biblioteca estándar proporciona un mecanismo poderoso y flexible que permite crear textos formateados de manera segura y eficiente, evitando las vulnerabilidades asociadas con los formatos dinámicos tradicionales.
¿Qué es std.fmt.format?
La función std.fmt.format pertenece al paquete std.fmt y se utiliza para generar una cadena de caracteres a partir de una plantilla (template) y una lista de argumentos. La sintaxis se asemeja al formato de printf, pero con la ventaja de que Zig garantiza la seguridad del tipo y la gestión de memoria.
Declaración de la función
pub fn format(
out: anytype,
comptime fmt: []const u8,
args: anytype,
) !void
Los parámetros son:
out: el destino donde se escribirá la cadena formateada. Puede ser una instancia destd.io.Writero un buffer.fmt: la plantilla de formato, marcada comocomptimepara que el compilador pueda verificarla en tiempo de compilación.args: los valores que se sustituirán en la plantilla.
Tipos de especificadores de formato
La función admite una serie de especificadores que indican cómo se deben representar los argumentos:
%s– cadena de caracteres%d– entero decimal%x– entero hexadecimal (minúsculas)%X– entero hexadecimal (mayúsculas)%b– entero binario%c– carácter%T– el nombre del tipo (útil para depuración)
Ejemplo básico: formato de cadena simple
// Importamos la biblioteca estándar
const std = @import("std");
// Función principal
pub fn main() !void {
// Creamos un writer que escribe en la salida estándar (stdout)
const stdout = std.io.getStdOut().writer();
// Usamos std.fmt.format para escribir en stdout
try std.fmt.format(
stdout, // 1. Destino donde se escribirá
"Hola, {s}! Tienes {d} puntos.\n", // 2. Plantilla de formato
.{ "Zig", 42 } // 3. Argumentos correspondientes
);
}
En el ejemplo anterior, el compilador comprueba que la plantilla contiene dos especificadores y que se proporcionan dos argumentos. La función devuelve un error si algo sale mal, por lo que usamos try para propagarlo.
Formateo posicional y nombrado
Zig permite referenciar argumentos por posición o por nombre, lo que aumenta la legibilidad del código.
Ejemplo posicional
try std.fmt.format(
stdout,
"El {1:s} es de color {0:s}.",
.{ "azul", "pescado" }
);
El primer argumento corresponde al índice 0, el segundo al índice 1. La salida será “El pescado es de color azul.”
Ejemplo nombrado
try std.fmt.format(
stdout,
"El {animal:s} se mueve con {velocidad:0>4d} km/h.",
.{ .animal = "tortuga", .velocidad = 5 }
);
Aquí el especificador {velocidad:0>4d} indica que el número debe ocupar al menos 4 espacios, rellenado con ceros a la izquierda.
Formateo de tipos complejos
La biblioteca std.fmt incluye soporte para tipos que implementan la interfaz std.fmt.Formatter. Un ejemplo clásico es el formato de fechas y horas, pero también se pueden formatear estructuras personalizadas.
Struct con formateo personalizado
const std = @import("std");
const Person = struct {
name: []const u8,
age: u32,
// Implementación de Formatter
fn format(self: @This(), fmt: anytype, options: anytype) anyerror!void {
// Simplemente delegamos a std.fmt.format
try fmt.print("Person {{ name: {s}, age: {d} }}", .{ self.name, self.age });
}
};
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const p = Person{ .name = "Ana", .age = 30 };
// Ahora podemos formatear la struct directamente
try std.fmt.format(stdout, "Datos: {any}\n", .{ p });
}
El método format de la struct recibe un writer y escribe la representación formateada. Cuando std.fmt.format encuentra el especificador {any}, llama automáticamente al método format del tipo.
Uso con buffers en memoria
Para construir cadenas en memoria antes de enviarlas a la salida, se puede usar un buffer mutable:
var buffer: [128]u8 = undefined;
const bufferWriter = std.io.fixedBufferStream(&buffer).writer();
try std.fmt.format(
bufferWriter,
"Resultado: {d} y {s}",
.{ 7 * 6, "exitoso" }
);
// Ahora buffer contiene la cadena formateada
std.debug.print("Buffer: {s}\n", .{ &buffer[0 .. bufferWriter.getWritten()] });
El método getWritten devuelve la cantidad de bytes realmente escritos, lo que permite extraer la subcadena correcta.
Control de errores y validación en tiempo de compilación
La función std.fmt.format es comptime-segura, lo que significa que el compilador verifica que:
- El número de especificadores coincida con los argumentos.
- Los tipos de los argumentos sean compatibles con los especificadores.
Si se produce una incompatibilidad, el compilador generará un error en tiempo de compilación, evitando fallos en tiempo de ejecución.
Ejemplo avanzado: generación de JSON formateado
Supongamos que necesitamos crear un objeto JSON sencillo. En lugar de concatenar strings, podemos usar std.fmt.format para garantizar la correcta escapada y formato.
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const name = "Bob";
const score = 88;
try std.fmt.format(
