El borrow checker de Rust: cómo funciona y cómo interpretar sus errores

El borrow checker es el componente del compilador de Rust que analiza el código en tiempo de compilación para garantizar que no existan referencias inválidas ni accesos a memoria liberada. No es un análisis dinámico: no hay ningún coste en runtime. Todo ocurre antes de generar el binario.

Qué analiza el borrow checker

Para cada referencia en el programa, el borrow checker calcula:

  • Su lifetime: el intervalo de código en que la referencia es válida.
  • Si coexiste con otras referencias que violan las reglas de borrowing.
  • Si el valor al que apunta vive suficiente tiempo.

Si alguna de estas condiciones no se cumple, el compilador emite un error y rechaza el programa.

Non-Lexical Lifetimes (NLL)

Antes de Rust 2018, el borrow checker usaba el scope léxico: una referencia se consideraba "viva" hasta la llave de cierre del bloque donde se declaraba, aunque dejara de usarse antes. Eso producía errores frustrantes. Con NLL, el borrow checker calcula el lifetime real: desde la primera hasta la última línea donde se usa la referencia.

fn main() {
    let mut v = vec![1, 2, 3];

    let primero = &v[0]; // referencia inmutable
    println!("{}", primero); // último uso de primero

    v.push(4); // OK con NLL: primero ya no está viva
    println!("{:?}", v);
}

Sin NLL, este código fallaba porque primero seguía "vivo" hasta el final del bloque. Con NLL, el compilador sabe que su lifetime termina en println!.

Error E0502: borrow inmutable y mutable simultáneos

fn main() {
    let mut s = String::from("hola");
    let r = &s;         // borrow inmutable
    s.push_str(" mundo"); // borrow mutable implícito
    println!("{}", r);
}
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let r = &s;
  |             -- immutable borrow occurs here
4 |     s.push_str(" mundo");
  |     ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("{}", r);
  |                    - immutable borrow later used here

Solución: usa r antes de modificar s, o no guardes la referencia inmutable.

fn main() {
    let mut s = String::from("hola");
    println!("{}", &s);   // uso inmediato, sin guardar referencia
    s.push_str(" mundo"); // OK
    println!("{}", s);
}

Error E0505: move mientras existe un borrow

fn consumir(s: String) {}

fn main() {
    let s = String::from("hola");
    let r = &s;
    consumir(s); // ERROR: move mientras r está viva
    println!("{}", r);
}
error[E0505]: cannot move out of `s` because it is borrowed
  --> src/main.rs:6:14
   |
5  |     let r = &s;
   |             -- borrow of `s` occurs here
6  |     consumir(s);
   |              ^ move out of `s` occurs here
7  |     println!("{}", r);
   |                    - borrow later used here

Solución: usa o descarta la referencia antes de mover el valor.

Error E0506: asignar a valor prestado

fn main() {
    let mut s = String::from("hola");
    let r = &mut s;
    s = String::from("otro"); // ERROR: s está prestado
    println!("{}", r);
}
error[E0506]: cannot assign to `s` because it is borrowed

Solución: modifica a través de la referencia mutable o descártala primero.

Cómo leer los errores del borrow checker

Los errores del borrow checker siempre tienen la misma estructura:

  1. Qué operación falla ("cannot borrow", "cannot move"…).
  2. Por qué falla ("because it is also borrowed as immutable").
  3. Dónde se creó el borrow que lo impide (con la flecha).
  4. Cuándo se usa por última vez ese borrow.

Leer las cuatro partes del error suele indicar la solución: reorganizar el código para que los lifetimes no se solapen.

Resumen

  • El borrow checker verifica en compilación que no existan referencias inválidas.
  • Con NLL, calcula el lifetime real de cada referencia, no el léxico.
  • E0502: inmutable y mutable activas a la vez.
  • E0505: move de un valor con un borrow activo.
  • E0506: asignación a un valor con un borrow activo.
  • La solución suele ser reorganizar el orden de uso de las referencias.

Una vez que entiendes el borrow checker, el siguiente concepto importante son los traits Copy y Clone: cuándo Rust copia un valor de forma implícita y cuándo necesitas pedírselo explícitamente.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP