Un vector en Zig es un conjunto de valores del mismo tipo pensado para operaciones SIMD. Cuando hablamos de “vectores” en otros lenguajes solemos pensar en listas dinámicas, pero en Zig @Vector significa otra cosa.
En Zig, cuando decimos vector, nos referimos a un tipo de longitud fija sobre el que aplicamos una misma operación elemento a elemento.
Los procesadores modernos (x64 con AVX, ARM con NEON) tienen la capacidad de realizar la misma operación matemática sobre varios números al mismo tiempo. Esto se conoce como SIMD (Single Instruction, Multiple Data).
Tradicionalmente, programar esto era una pesadilla de funciones ilegibles y no portables. Zig cambia las reglas del juego: hace que el código SIMD sea nativo, limpio y portable.
¿Qué es @Vector?
Un @Vector(longitud, tipo) en Zig es un conjunto de tamaño fijo de elementos primitivos (enteros, flotantes o punteros) que se procesan en paralelo.
A diferencia de un Array ([N]T), que sirve para almacenar datos y acceder a ellos individualmente, un Vector está diseñado para operar con todos los datos a la vez.
Declaración y uso
La sintaxis es sencilla. Usamos la función incorporada (built-in) @Vector.
const std = @import("std");
pub fn main() void {
// Definimos un vector de 4 flotantes (f32)
const a = @Vector(4, f32){ 1.0, 2.0, 3.0, 4.0 };
const b = @Vector(4, f32){ 5.0, 6.0, 7.0, 8.0 };
// Operación matemática directa
const c = a + b;
// c es ahora { 6.0, 8.0, 10.0, 12.0 }
std.debug.print("Resultado: {any}\n", .{c});
}Fíjate en a + b: no hemos escrito un bucle for, pero la operación sigue siendo elemento a elemento. El compilador puede generar una o varias instrucciones SIMD según la longitud del vector y la arquitectura de destino.
Operaciones vectoriales
Casi todos los operadores aritméticos y lógicos funcionan con vectores:
- Aritmética:
+,-,*,/(suman/multiplican elemento a elemento). - Lógica:
&,|,^,~. - Comparación:
==,<,>. (¡Ojo! Esto devuelve un vector de booleanos).
const v1 = @Vector(4, i32){ 10, 20, 30, 40 };
const v2 = @Vector(4, i32){ 10, 50, 30, 5 };
// Comparamos elemento a elemento
const mask: @Vector(4, bool) = (v1 == v2);
// mask es { true, false, true, false }Difusión con @splat
Para multiplicar todos los elementos por un escalar, primero repetimos ese valor en un vector mediante @splat.
const v = @Vector(4, f32){ 1.1, 2.2, 3.3, 4.4 };
const escala = 2.0;
// El escalar se "expande" a un vector {2.0, 2.0, 2.0, 2.0}
const resultado = v * @as(@Vector(4, f32), @splat(escala));El tipo de retorno de @splat se infiere del contexto. Por eso @as(@Vector(4, f32), @splat(escala)) indica tanto el tipo de elemento como la longitud del resultado.
Portabilidad automática
Las operaciones más cortas que el ancho SIMD nativo suelen compilarse a una instrucción. Las más largas pueden necesitar varias y, si una operación no tiene soporte SIMD en el objetivo, el compilador opera elemento a elemento.
Si escribes este código y compilas para tu PC (x86_64), Zig usará registros SSE o AVX.
Si compilas para una Raspberry Pi (aarch64), Zig usará instrucciones NEON.
Si compilas para un microcontrolador antiguo que no tiene SIMD, Zig generará un bucle normal automáticamente (polyfill).
El código no fija AVX, NEON ni otra familia concreta. La selección depende del objetivo y de las características de CPU indicadas durante la compilación.
Arrays y vectores: cuándo usar cada uno
Es fácil confundirse porque ambos parecen “listas de cosas”.
| Característica | Array [N]T / Slice []T | Vector @Vector(N, T) |
|---|---|---|
| Objetivo | Almacenar y organizar datos. | Realizar cálculos matemáticos. |
| Acceso | Individual (arr[i]) es rápido. | Individual puede ser lento. |
| Memoria | Estructurada linealmente. | Carga en registros de CPU anchos. |
| Uso típico | Strings, buffers, inventarios. | Física, audio, gráficos, criptografía. |
No uses @Vector para todo. Úsalo cuando tengas bucles intensivos de cálculo (como procesar una imagen, mezclar audio o multiplicar matrices).
Conversión entre arrays y vectores
A menudo tendrás tus datos en un array (leído de un archivo) y querrás procesarlo rápido. Debes cargarlo en un vector.
const array = [_]f32{ 1.0, 2.0, 3.0, 4.0 };
// Casteo de array a vector (si el tamaño coincide y es conocido)
const v: @Vector(4, f32) = array;
// ... operar con v ...
// Volver a array
const resultado_array: [4]f32 = v;Ejemplo práctico: producto escalar
El producto escalar es una operación fundamental en gráficos 3D y juegos. Multiplicas componentes y sumas el resultado.
pub fn dotProduct(a: @Vector(4, f32), b: @Vector(4, f32)) f32 {
const mult = a * b; // { ax*bx, ay*by, az*bz, aw*bw }
// @reduce aplica una operación a todos los elementos del vector
return @reduce(.Add, mult);
}La función @reduce aplica una reducción horizontal secuencial y convierte el vector en un solo valor.