¿Conoces esa sensación de cuando empiezas un nuevo lenguaje y esperas correr un sprint, pero en su lugar tropiezas en el primer paso? Así me pasó con Rust. Tenía una utilidad de JavaScript que funcionaba perfectamente —algo que analizaba y mutaba algunos arrays— y pensé que la reescribiría durante un descanso para tomar café. Abrí mi editor, escribí lo que me pareció un código natural y ejecuté el compilador. Explotó. No con errores de sintaxis. No con puntos y coma faltantes. Me dijo que había intentado usar un valor que ya había sido movido (moved). Me quedé mirando la pantalla. ¿Movido? Yo no había movido nada. Simplemente lo asigné a otra variable.

La zona de confort de JavaScript

JavaScript te entrena para tratar los datos como una pizarra comunitaria. Declaras un objeto o un array, se lo entregas a una función, dejas que esa función añada una propiedad y luego se lo entregas a otro lugar. El recolector de basura (garbage collector) se sienta en segundo plano como un conserje invisible, esperando para barrer cualquier cosa que abandones. Nunca te preguntas: "¿Quién es el dueño de este string?". Te preguntas: "¿Puedo leerlo?". Si la respuesta es sí, lo tocas. Si necesitas otra referencia, simplemente dices let b = a y sigues adelante. Ambas variables apuntan al mismo bloque de memoria y, si a lo muta, b ve el cambio inmediatamente. Es conveniente. También es caótico, pero el caos rara vez te muerde porque el entorno de ejecución (runtime) se encarga de la limpieza.

El primer choque con el compilador

Rust no confía en ti. Suena duro, pero es lo primero que aprendes. El lenguaje está construido en torno a un sistema de propiedad (ownership) con tres reglas rígidas. Primero, cada valor tiene exactamente un dueño. Segundo, solo hay un dueño a la vez. Tercero, cuando ese dueño sale del ámbito (scope), Rust elimina el valor automáticamente. Sin recolector de basura. Sin conteo de referencias en segundo plano a menos que optes explícitamente por ello. Solo estas tres reglas, aplicadas por el compilador antes de que tu código se ejecute.

Cuando escribí mi versión en Rust de esa utilidad de JavaScript, hice lo que me pareció natural. Creé un vector, que es la respuesta de Rust a un array, y lo asigné a una segunda variable. Luego intenté usar la primera. El compilador se negó. En JavaScript, let b = a copia la referencia. En Rust, mueve la propiedad (ownership). La variable original deja de ser válida. El compilador hace esto para garantizar que nunca tengas dos rutas pisando accidentalmente la misma memoria, que es como ocurren las condiciones de carrera (data races) y los errores de uso después de la liberación (use-after-free) en otros sistemas.

Por qué Rust te quita tus juguetes

Mira este patrón en JavaScript:

let a = [1, 2, 3];
let b = a;
a.push(4);
// Both a and b see 4.

Es algo natural. No lo pensarías dos veces. En Rust, esa misma intuición hará que el compilador te rechace antes de que termines de escribir. Una vez que b es el dueño del vector, a es un nombre vacío. No puedes añadir elementos (push) en él. Ni siquiera puedes mirarlo. La memoria ahora le pertenece a b.

Esto parece un castigo hasta que te das cuenta de lo que evita. Si dos variables pudieran mutar los mismos datos en el heap sin coordinación, correrías el riesgo de corromper la memoria. Rust elimina toda esa categoría de errores al hacer que la transferencia sea explícita. El compilador no está siendo pedante. Está actuando como un guardián. Te obliga a decidir, en cada paso, qué parte de tu código es responsable de qué datos.

Borrowing: El truco que en realidad es una funcionalidad

Por supuesto, si cada asignación transfiriera la propiedad para siempre, escribir programas sería agotador. Tendrías que clonarlo todo, desperdiciando memoria y velocidad. Rust resuelve esto con el préstamo (borrowing).

El borrowing te permite usar un valor sin tomarlo. Hay dos tipos, y la distinción es importante.

  • Préstamos inmutables, escritos como &T: Estos te permiten leer datos. Puedes tener tantos como quieras al mismo