rust-stack-vs-heap

Stack vs Heap en Rust: gestión de memoria esencial

  • 6 min

El Stack y el Heap son dos zonas de memoria con reglas diferentes para guardar datos.

Si vienes de lenguajes como JavaScript, Python o C#, probablemente no hayas tenido que liberar memoria de forma explícita. Un recolector de basura se encarga de detectar los objetos que ya no se usan, a cambio de cierto consumo adicional y pausas que dependen de cada runtime.

En C podemos pedir memoria manualmente con malloc y liberarla con free. C++ también permite hacerlo, aunque el código moderno suele apoyarse en RAII y punteros inteligentes para automatizar la liberación.

Rust toma un tercer camino. Para entender cómo logra seguridad sin recolector de basura, primero debemos entender dónde viven nuestros datos. En la memoria RAM de tu ordenador, tu programa utiliza principalmente dos zonas: el Stack (Pila) y el Heap (Montón).

El stack (la pila)

El Stack es una estructura de datos tipo LIFO (Last In, First Out - Último en entrar, primero en salir).

Imagina una pila de platos en un restaurante.

  1. Cuando lavas un plato, lo pones encima de la pila (Push).
  2. Cuando necesitas un plato, coges el de arriba (Pop).
  3. No puedes sacar un plato del medio sin liar un desastre.

Características del stack

  • Asignación muy rápida: Normalmente basta con desplazar el stack pointer para reservar o recuperar espacio de una llamada.
  • Tamaño conocido: Los valores almacenados directamente en un marco de pila tienen un tamaño conocido en compilación.
  • Buena localidad: Los marcos de las llamadas ocupan regiones contiguas de memoria virtual, algo que suele favorecer a la caché del procesador.

¿Qué se guarda aquí?

Los tipos primitivos que vimos antes, cuyo tamaño nunca cambia:

  • Enteros (i32, u64…)
  • Flotantes (f64…)
  • Booleanos (bool)
  • Arrays de tamaño fijo ([i32; 5])

De forma conceptual, cada llamada crea un marco con sus parámetros y variables locales. Cuando la función termina, ese marco deja de estar disponible (aunque el compilador puede guardar algunos valores en registros u optimizarlos por completo).

El heap (el montón)

¿Qué pasa si queremos guardar una lista que crece dinámicamente (como un Vector) o un texto que introduce el usuario (String)? No sabemos cuánto ocupará al compilar. No podemos ponerlo en el Stack.

Para esos casos usamos el Heap. El Heap es menos organizado; es como un restaurante enorme con muchas mesas.

Cuando quieres poner algo en el Heap:

  1. Pides una cierta cantidad de memoria.
  2. El asignador de memoria busca un hueco libre lo suficientemente grande y, cuando hace falta, solicita más memoria al sistema operativo.
  3. Marca ese hueco como “ocupado”.
  4. Te devuelve un puntero (la dirección de memoria de esa mesa).

Características del heap

  • Más lento de asignar: El sistema tiene que buscar hueco (bookkeeping).
  • Acceso indirecto: Para llegar al dato hay que seguir un puntero, lo que puede empeorar la localidad y la caché frente a un valor almacenado directamente.
  • Dinámico: Puedes pedir más memoria o liberarla en tiempo de ejecución.

La conexión entre stack y heap

En tipos como String o Vec<T>, los elementos reservados dinámicamente viven en el heap, mientras que la estructura que guarda el puntero, la longitud y la capacidad suele formar parte de la variable local.

Imagina que el Heap es la sala del restaurante y el Stack es la lista de reservas en la entrada. Tú (el procesador) miras la lista (Stack) para saber en qué mesa (Heap) están tus comensales.

Comparativa rápida

CaracterísticaStack (Pila)Heap (Montón)
OrganizaciónMarcos de llamadas en orden LIFOBloques gestionados por un asignador
VelocidadMuy rápidaMás lenta
Tamaño de datosFijo y conocidoDinámico y cambiante
Coste de gestiónMuy bajoMayor al asignar y liberar
Ejemplo RustEl valor de un i32 o [i32; 4] localEl contenido dinámico de String, Vec<i32> o Box<T>

El problema de la limpieza

Los lenguajes se diferencian mucho en cómo gestionan esto. Recuperar un marco del stack es sencillo; el problema interesante es saber cuándo liberar cada reserva del heap.

  1. En C: El programador debe coordinar llamadas como malloc y free. Si se olvida de liberar una reserva aparece una fuga; si la libera antes de tiempo puede dejar un puntero colgante.
  2. En C++: RAII permite ligar los recursos a la vida de objetos, aunque el lenguaje sigue admitiendo gestión manual y accesos inseguros.
  3. En Java, Go o Python: Un recolector detecta los objetos inalcanzables y recupera su memoria. Consume recursos y puede introducir pausas, aunque su comportamiento depende mucho de la implementación.

La solución de Rust: ownership

Rust liga cada recurso a un propietario. Cuando el propietario sale de su ámbito, Rust ejecuta la lógica de drop correspondiente, que en tipos como String o Vec<T> libera su reserva del heap.

Sin Garbage Collector. Sin free manual.

fn main() {
    {
        // 's' es un String.
        // El texto "hola" se guarda en el HEAP.
        // La variable 's' (que contiene el puntero, longitud y capacidad) vive en el STACK.
        let s = String::from("hola");

        // Hacemos cosas con s...
    }
    // <--- Aquí termina el ámbito (scope).
    // Rust ve que 's' desaparece del Stack.
    // Automáticamente, Rust llama a una función especial (drop) que libera la memoria del Heap.
}
Copied!

Esta idea, aparentemente simple, tiene implicaciones profundas. ¿Qué pasa si copiamos s? ¿Podemos tener dos punteros en el Stack apuntando al mismo sitio en el Heap?

Si hiciéramos eso y ambos intentaran liberar la memoria al final… tendríamos un error de Double Free. Rust impide esto mediante el sistema de Ownership (Propiedad).

¿Por qué es importante saber esto?

Entender Stack vs Heap es importante en Rust porque el lenguaje te obliga a decidir dónde poner las cosas.

  • Si usas let x = 5;, es barato y simple (Stack).
  • Si usas let x = Box::new(5);, reservas ese 5 en el Heap (con un coste de asignación y hasta que se destruya su propietario).
  • Si copias datos del Stack, es rápido (copiar bits).
  • Si clonas una colección con datos en el Heap, normalmente hay que reservar memoria y copiar sus elementos.