Un array en Zig es una colección de tamaño fijo cuyo tamaño forma parte del tipo. Si vienes de JavaScript, Python o C#, probablemente pienses en un “Array” como una lista dinámica, pero en Zig necesitamos separar arrays y slices.
En Zig conviene separar dos conceptos:
- Array (
[N]T): contiene los datos en el propio valor y su tamaño se conoce al compilar. - Slice (
[]T): es una vista de datos almacenados en otro lugar; lleva un puntero y una longitud.
Entender esta diferencia ayuda a evitar copias y expresar la propiedad de los datos.
El array ([N]T)
Un array en Zig es un bloque de memoria contiguo de un tamaño fijo. La clave aquí es que el tamaño es parte del tipo.
Esto significa que un array de 5 enteros ([5]i32) es un tipo de dato diferente a un array de 4 enteros ([4]i32). No son compatibles entre sí.
// Declaración de un array
const numeros = [5]i32{ 1, 2, 3, 4, 5 };
// Inferencia de tamaño con '_'
// El compilador cuenta los elementos por nosotros
const vocales = [_]u8{ 'a', 'e', 'i', 'o', 'u' };Características del array
- Contiene sus elementos en línea: la ubicación concreta depende de dónde y cómo se declare el valor.
- Tiene semántica de valor: al asignarlo a otra variable, se copian sus elementos.
var a = [_]u8{ 1, 2, 3 };
var b = a; // ¡COPIA COMPLETA!
b[0] = 9;
// 'a' sigue siendo {1, 2, 3}
// 'b' es {9, 2, 3}Copiar un array grande puede ser costoso. Para compartir una vista de sus elementos sin copiar los datos usamos slices.
El slice ([]T)
Un slice no guarda los elementos por sí mismo. Es una vista que apunta a datos almacenados en otro lugar.
Internamente, un Slice se compone de dos cosas:
- Un puntero al primer elemento.
- Una longitud
lenque indica cuántos elementos contiene la vista.
Podemos pensar en el Array como el “terreno físico” y en el Slice como la “cámara” que enfoca una parte de ese terreno.
const array_real = [_]u8{ 10, 20, 30, 40, 50 };
// Creamos un slice que ve todo el array
const slice_total: []const u8 = &array_real;
// Creamos un slice que ve solo del índice 1 al 2 (exclusivo el 3)
const vista_parcial: []const u8 = array_real[1..3];
// vista_parcial apunta a { 20, 30 } y su len es 2Sintaxis de slicing [inicio..fin]
La sintaxis inicio..fin usa un intervalo semiabierto:
inicioes inclusivo.fines exclusivo.- El índice inicial debe indicarse.
- Si omites
fin, el slice llega hasta el final.
const todo = array[0..]; // Slice de todo el arrayPor qué los slices son tan importantes
Lo interesante de los Slices es que nos permiten escribir funciones genéricas respecto al tamaño.
Si escribimos una función que acepta un array, estamos atados a ese tamaño:
// Solo acepta arrays de EXACTAMENTE 5 enteros
fn procesar(datos: [5]i32) void { ... }Pero si aceptamos un Slice, la función acepta cualquier cantidad de datos:
// Acepta CUALQUIER cantidad de enteros
fn procesar(datos: []const i32) void {
for (datos) |elem| {
// ...
}
}
pub fn main() void {
const pequeno = [_]i32{1, 2};
const grande = [_]i32{1, 2, 3, 4, 5, 6};
// Zig convierte automáticamente el puntero al array en un slice
procesar(&pequeno);
procesar(&grande);
}Idea práctica: cuando una función deba aceptar una cantidad variable de elementos contiguos, normalmente conviene recibir un slice ([]T o []const T). Así evitamos atarla a una longitud concreta.
Comprobación de límites
Leer fuera de los límites de un array o slice es comportamiento ilegal. Zig incorpora comprobaciones de límites en los modos seguros, como Debug y ReleaseSafe.
Si el índice solo se conoce durante la ejecución, esos modos detienen el programa con un panic antes de acceder a otra zona de memoria.
const numeros = [_]u8{ 1, 2, 3 };
const slice = numeros[0..];
// En Debug y ReleaseSafe, esto provoca un panic en tiempo de ejecución.
const valor_fuera_de_rango = slice[5];Si el índice se conoce al compilar, Zig muestra un error de compilación. En ReleaseFast y ReleaseSmall se desactivan por defecto varias comprobaciones de seguridad, por lo que no debemos depender del panic para validar datos externos.
Slices y punteros
Es importante notar la diferencia en la declaración de tipos:
*T: Un puntero a un solo elemento.[*]T: Un puntero a “muchos” elementos (estilo C, sin longitud conocida, peligroso).[]T: Un Slice (Puntero + Longitud, seguro).
Cuando la longitud está disponible, un slice es preferible a un puntero de muchos elementos porque conserva esa información y permite comprobar los límites.