Para gestionar la memoria de un programa, históricamente existen dos enfoques principales con sus respectivos problemas. El primero es la gestión manual (usada en C o C++), donde el programador asigna y libera memoria explícitamente. Si se olvida de liberar memoria, ocurre un memory leak; si libera memoria dos veces (double free) o intenta usar una dirección que ya no es válida (dangling pointer), el programa falla o crea brechas de seguridad. Según datos de Microsoft y Google, estos errores de memoria causan aproximadamente el 70% de las vulnerabilidades de seguridad en software moderno.
El segundo enfoque es el uso de un Garbage Collector (GC) (usado en Java, Go o Python). Aquí, un proceso automático limpia la memoria que ya no se usa. Esto simplifica la vida del programador, pero introduce un “overhead” (coste de rendimiento) y pausas en la ejecución (stop-the-world) para que el recolector pueda trabajar, lo que impide un control total sobre el tiempo de respuesta.
Rust propone un “tercer camino” mediante el concepto de Ownership (propiedad). En lugar de limpiar la memoria en tiempo de ejecución con un recolector, o dejar la responsabilidad total al programador, Rust utiliza un sistema de reglas que el compilador verifica en tiempo de compilación. Si el código no cumple con estas reglas, simplemente no compila. Esto garantiza seguridad sin sacrificar rendimiento.
Para entender esto, debemos distinguir dos áreas de la memoria: el Stack (Pila) y el Heap (Montículo). El Stack es rápido y almacena datos de tamaño fijo y conocido en tiempo de compilación (como enteros). El Heap es para datos cuyo tamaño puede cambiar o es desconocido (como una cadena de texto dinámica). Rust te obliga a ser consciente de dónde vive cada dato para asegurar que, cuando una variable pierda su “propiedad”, la memoria en el Heap se libere inmediatamente y sin errores.
// Ejemplo de transferencia de propiedad (Ownership) en Rust
fn main() {
// Creamos un String. El valor "Hola" se almacena en el Heap.
// La variable 'nombre_uno' es la dueña de este dato en el Stack.
let nombre_uno = String::from("Hola");
// En Rust, asignar 'nombre_uno' a 'nombre_dos' no copia el dato.
// Se transfiere la propiedad (Move). 'nombre_uno' deja de ser válida.
let nombre_dos = nombre_uno;
// Imprimimos el valor de 'nombre_dos'
println!("El contenido de nombre_dos es: {}", nombre_dos);
// Si intentáramos usar 'nombre_uno' aquí, el compilador daría un error:
// println!("{}", nombre_uno); // ERROR: value borrowed here after move
}
Explicación del Código
let nombre_uno = String::from("Hola");: Aquí se declara una variable de tipoString. El métodoString::fromreserva memoria en el Heap para almacenar el texto “Hola”. La variablenombre_unocontiene los metadatos en el Stack (dirección de memoria, capacidad y longitud) para acceder a ese texto.let nombre_dos = nombre_uno;: Esta es la línea crucial. En otros lenguajes, esto podría ser una copia o un puntero compartido. En Rust, esto es un Move (traslado). La propiedad de la memoria en el Heap se transfiere anombre_dos. Para evitar el error de “double free” (liberar la misma memoria dos veces), el compilador marca anombre_unocomo inválida.println!("{}", nombre_dos);: Accedemos al valor denombre_doscon éxito, ya que es la dueña actual de la información.// println!("{}", nombre_uno);: Si intentáramos desomentar esta línea, el compilador de Rust lanzaría un error de compilación. Esto impide que intentemos acceder a una variable que ya no es dueña de una posición en el Heap, evitando así errores de memoria en tiempo de ejecución.
N° 2