wandres.dev
POR QUÉ RUST · seguridad sin GC, la mentalidad

El compilador como aliado

Por qué 'pelear con el borrow checker' es en realidad aprender a razonar sobre tus datos. El cambio de mentalidad que convierte al compilador de adversario en copiloto.

⏱ 13 min

Todo el que aprende Rust pasa por la misma experiencia: al principio siente que pelea contra el compilador. El borrow checker rechaza código que “obviamente funciona”, y la frustración es real. Esta lección reencuadra esa lucha. Lo que parece un adversario pedante es, en realidad, un profesor exigente enseñándote a razonar sobre datos con una precisión que ningún otro lenguaje te obliga a alcanzar. El cambio de chip no es aprender más sintaxis: es cambiar quién crees que es el compilador.

🎯 Al terminar esta lección sabrás
  • Reencuadrar el borrow checker como aliado, no como obstáculo.
  • Diseccionar un error real y entender qué bug previene.
  • Adoptar el modelo mental de dueños y tiempos de vida.
  • Aprender a leer los diagnósticos de rustc como un profesor.

Del adversario al copiloto

La queja “estoy peleando con el borrow checker” contiene un malentendido de fondo: supone que el código rechazado estaría bien. Casi nunca lo está. El borrow checker no inventa problemas; revela problemas de gestión de datos que ya estaban en tu diseño, solo que en otro lenguaje habrían viajado silenciosos hasta producción.

flowchart LR
A[El compilador rechaza tu codigo] --> B[Lectura 1: me estorba]
A --> C[Lectura 2: que me esta enseñando]
B --> D[Frustracion y parches al azar]
C --> E[Entiendes el problema real de tus datos]
style D fill:#f38ba8,color:#11111b
style E fill:#a6e3a1,color:#11111b

La transición de la lectura 1 a la 2 es, literalmente, el primer nivel de dominar Rust. No es cosmética: cambia lo que haces cuando ves un error rojo.

Anatomía de un error del borrow checker

Veamos el error más común de todos, y por qué es un regalo:

fn main() {
    let mut lista = vec![String::from("a"), String::from("b")];
    let primero = &lista[0];       // prestamos el primer elemento para leerlo
    lista.push(String::from("c")); // ...y a la vez pedimos mutar la lista
    println!("{primero}");
}

El compilador se niega, con un diagnóstico quirúrgico:

error[E0502]: cannot borrow `lista` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let primero = &lista[0];
  |                    ----- immutable borrow occurs here
4 |     lista.push(String::from("c"));
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("{primero}");
  |                ------- immutable borrow later used here

¿Es una pedantería? Al contrario: es una salvación. Un Vec<T> guarda sus elementos en un bloque contiguo del heap. Cuando haces push y no cabe, el vector reasigna: pide un bloque mayor, copia los datos y libera el viejo. Si primero siguiera apuntando al bloque antiguo, ahora sería un puntero colgante. Este es exactamente el bug de invalidación de iteradores que C++ compila sin rechistar y que provoca corrupciones imposibles de depurar. Rust lo convierte en tres líneas de error antes de ejecutar nada.

La solución no es engañar al compilador, es arreglar el diseño. Si solo necesitas leer antes de mutar, ordena los tiempos de vida:

fn main() {
    let mut lista = vec![String::from("a"), String::from("b")];
    {
        let primero = &lista[0];
        println!("primero es {primero}"); // usamos el prestamo...
    }                                     // ...y aqui muere, se libera el dato
    lista.push(String::from("c"));        // ahora no hay prestamo vivo: correcto
}
⚠️
Nunca 'venzas' al borrow checker a ciegas

La tentación del principiante es esparcir .clone(), Rc, RefCell o unwrap hasta que el rojo desaparezca. A veces funciona, pero si no entiendes por qué el error existía, has silenciado al profesor en vez de aprender la lección. Está bien clonar para avanzar (lo veremos en la lección de la curva), pero hazlo sabiendo qué estás evitando, no como un conjuro. El objetivo no es que compile: es entender por qué no compilaba.

El cambio de chip: dueños y tiempos de vida

Programar en Rust con fluidez es interiorizar tres preguntas y hacértelas antes de escribir, no cuando el compilador te frena:

🦀

¿Quién es el dueño?

De cada dato, ¿qué variable lo posee y es responsable de liberarlo? Si tú dudas, el borrow checker dudará contigo.

¿Cuánto vive?

¿Desde dónde hasta dónde existe este valor? Un préstamo jamás puede sobrevivir a su dueño.

🔀

¿Quién lo toca?

¿Se lee o se escribe, y hay alguien más accediendo a la vez? Recuerda: muchos lectores o un escritor.

Estas preguntas no son de Rust: son preguntas sobre la naturaleza de tu programa que siempre existieron. En C las contestabas mal y te enterabas en producción; en Java las ignorabas y pagabas con el GC. Rust te obliga a contestarlas bien, y por escrito, en el código.

Los mensajes de error como profesor

Hay una razón por la que rustc es célebre: sus errores no dicen solo qué falló, sino dónde, por qué y a menudo cómo arreglarlo, con sugerencias que puedes aplicar con cargo fix. Compara la cultura: un segmentation fault (core dumped) de C no te dice nada; el E0502 de arriba te da tres líneas señaladas y el nombre exacto del conflicto. El compilador de Rust está diseñado, deliberadamente, como material didáctico. Aprender a leerlo entero, con calma, es la habilidad que más acelera tu progreso, más que cualquier búsqueda a ciegas en internet.

El borrow checker externaliza el razonamiento que un maestro ya hacía

Aquí está la revelación que lo cambia todo. Los grandes programadores de C y C++, los que escriben código robusto durante décadas, llevan en la cabeza, en todo momento, un modelo de quién posee cada puntero, cuánto vive y quién puede tocarlo. Ese modelo es lo que separa al que escribe kernels del que escribe segfaults. El problema es que vive en su cabeza: es tácito, no verificable, y se derrumba en cuanto el proyecto crece o entra otra persona. Lo que hace Rust es coger ese modelo mental de los expertos y convertirlo en parte del lenguaje, obligatorio y comprobado por la máquina. “Pelear con el borrow checker” es, en realidad, el proceso de construir en tu propia cabeza ese modelo que los maestros de C tenían tácito, con la diferencia de que en Rust no puedes hacer trampa ni olvidarte. Por eso la lucha es temporal: no estás aprendiendo a apaciguar a un compilador caprichoso, estás instalando en tu intuición el razonamiento sobre datos que te hará mejor programador en todos los lenguajes, para siempre. El día que el borrow checker deje de sorprenderte no será porque te rendiste: será porque ahora piensas como él, y eso es exactamente lo que Rust quería enseñarte.

⚔️ Cambia de bando con el compilador
  1. Reproduce el error E0502 del Vec. En vez de arreglarlo a lo loco, escribe en una frase qué bug concreto te está evitando (pista: la reasignación del vector).
  2. Arréglalo de dos formas distintas: acotando el ámbito del préstamo, y clonando el elemento con .clone(). Razona qué cuesta cada una.
  3. La próxima vez que un error rojo te frustre, para y pregúntate las tres preguntas: quién es el dueño, cuánto vive, quién lo toca. La respuesta al error casi siempre está ahí.