Creación de un vector de ceros en Rust: magia bajo el capó

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Vector de ceros en Rust: no es tan simple como parece

\n\n

Podría parecer que no hay nada más fácil que crear un vector lleno de ceros. En Python se hace en una línea: [0] * n. En JavaScript: Array(n).fill(0). Pero cuando pasas a Rust, incluso una tarea tan trivial abre todo un mundo de programación de sistemas, gestión de memoria y abstracciones de costo cero.

\n\n

La pregunta "Creating a vector of zeros for a specific size" en Stack Overflow ha acumulado miles de visitas. Y no es casualidad: detrás de una consulta simple se esconde una comprensión profunda de cómo Rust maneja la memoria, la inicialización y la seguridad. Hoy miraremos bajo el capó de Rust y analizaremos todas las formas de crear un vector de ceros y, lo más importante, entenderemos qué sucede realmente en la memoria RAM.

\n\n

¿Por qué es más complicado que en Python?

\n\n

En Python o JavaScript simplemente creas un array, y el intérprete hace todo el trabajo sucio por ti. Rust, en cambio, es un lenguaje de nivel de sistema. No permite dejar la memoria sin inicializar (a menos que uses unsafe). Cada byte debe estar explícitamente lleno o marcado como "comportamiento indefinido".

\n\n

Imagina: pides 100 cajas vacías. En Python, simplemente te traerán 100 cajas vacías. En Rust, el repartidor preguntará: "¿Qué pongo dentro de cada una?" — y si no lo dices, simplemente no vendrá. Esto garantiza que nunca leas accidentalmente "basura" de la memoria dejada por un programa anterior.

\n\n

Método n.º 1: El clásico vec![0; size]

\n\n

La forma más obvia y recomendada es usar la macro vec!:

\n\n
let zeros: Vec<i32> = vec![0; 10];\nprintln!("{:?}", zeros); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
\n\n

Corto, elegante, claro. Pero, ¿qué sucede bajo el capó? Vamos a desglosarlo paso a paso.

\n\n

Bajo el capó de vec![0; 10]

\n\n

Cuando escribes vec![0; 10], Rust hace lo siguiente:

\n\n
    \n
  1. Asigna memoria en el heap para 10 elementos de tipo i32. Cada i32 ocupa 4 bytes, totalizando 40 bytes.
  2. \n
  3. Verifica que el tipo implemente el trait Clone. Los números i32 lo implementan, por lo que el compilador puede copiar el valor 0 para cada elemento.
  4. \n
  5. Ejecuta un bucle de copia dentro de alloc::vec::from_elem. El compilador optimiza este bucle a memset (si el tipo es un escalar simple).
  6. \n
  7. Devuelve un Vec con una longitud de 10 y una capacidad de 10.
  8. \n
\n\n

Punto clave: la memoria no queda sin inicializar. Rust garantiza que cada byte se llenará con ceros antes de que accedas al vector.

\n\n

Método n.º 2: vec![Default::default(); size]

\n\n

Si tu tipo no es un número pero implementa el trait Default, puedes usar un enfoque genérico:

\n\n
#[derive(Default)]\nstruct Point {\n    x: f64,\n    y: f64,\n}\n\nlet points: Vec<Point> = vec![Default::default(); 5];\nprintln!("{:?}", points[0]); // Point { x: 0.0, y: 0.0 }
\n\n

Para tipos primitivos, Default::default() da 0, 0.0, false o '\\0'. Para estructuras personalizadas, lo que hayas definido en #[derive(Default)].

\n\n

¿Y qué hay del rendimiento?

\n\n

Escribamos un benchmark simple (en código real usa criterion):

\n\n
use std::time::Instant;\n\nfn main() {\n    let n = 1_000_000;\n\n    let start = Instant::now();\n    let v1 = vec![0u8; n];\n    let dur1 = start.elapsed();\n\n    let start = Instant::now();\n    let v2 = vec![0u64; n];\n    let dur2 = start.elapsed();\n\n    println!("u8:  {:?}", dur1);\n    println!("u64: {:?}", dur2);\n}
\n\n

En

Blogs

Book Recommendations