Estructuras auto-referenciales en Rust: tutorial paso a paso

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Por qué Rust no te deja almacenar una referencia a sí mismo: lo analizamos con ejemplos

\n\n

Imagina la situación: estás escribiendo un programa en Rust y necesitas crear una estructura que almacene algunos datos, así como una referencia a esos mismos datos. ¿Lógico? ¡Claro que sí! Pero el compilador de Rust de repente da un error y te quedas perplejo. ¿Te suena familiar? Esta pregunta es una de las más populares en Stack Overflow (miles de visitas), y hoy la analizaremos de principio a fin.

\n\n

En este tutorial paso a paso, no solo entenderemos por qué no se puede almacenar un valor y una referencia en la misma estructura Rust, sino que también aprenderemos a sortear esta limitación de forma segura. ¡Allá vamos!

\n\n

El problema: un intento ingenuo

\n\n

Supongamos que queremos crear una estructura que almacene una cadena y su primera letra (una referencia al primer carácter). Así es como se ve en el código:

\n\n
struct MyStruct {\n    data: String,\n    first_char: &str,  // referencia a data\n}\n\nfn main() {\n    let s = String::from("Hello");\n    let first = &s[0..1];  // referencia al primer carácter\n    \n    let my_struct = MyStruct {\n        data: s,\n        first_char: first,  // intentamos guardar la referencia\n    };\n}\n
\n\n

El compilador muestra un error que desconcierta a muchos principiantes. Analicemos qué salió mal exactamente.

\n\n

¿Por qué Rust prohíbe las estructuras auto-referenciales?

\n\n

La raíz del problema está en el sistema de propiedad (ownership) y préstamo (borrowing) de Rust. Cuando mueves (move) el valor s al campo data, este se copia físicamente a una nueva ubicación en la memoria. La referencia first mientras tanto sigue apuntando a la dirección anterior, que ahora es inválida.

\n\n

En Rust, cada referencia debe ser válida durante todo su tiempo de vida. Las estructuras auto-referenciales en Rust violan esta regla porque la referencia y el valor viven en el mismo objeto, y al mover la estructura, la referencia se vuelve colgante (dangling reference).

\n\n

¿Qué sucede bajo el capó?

\n\n

Cuando escribes este código:

\n\n
let s = String::from("Hello");\nlet first = &s[0..1];\nlet my_struct = MyStruct { data: s, first_char: first };\n
\n\n

Ocurre lo siguiente:

\n
    \n
  1. s es dueño de la cadena "Hello" en el heap.
  2. \n
  3. first es una referencia a una parte de esa cadena.
  4. \n
  5. Al crear la estructura con data: s, la propiedad de la cadena se transfiere a la estructura. La cadena puede moverse en la memoria.
  6. \n
  7. La referencia first sigue apuntando a la dirección anterior — esto es una referencia colgante. Rust no puede permitirlo.
  8. \n
\n\n

El Borrow checker de Rust detecta esta posible inseguridad y bloquea el código en tiempo de compilación. No es un error, sino una característica que evita muchos problemas en tiempo de ejecución.

\n\n

Escenarios reales donde se necesitan estructuras auto-referenciales

\n\n

A pesar de la prohibición, estas estructuras pueden ser útiles. Estos son casos típicos:

\n\n
    \n
  • Analizadores sintácticos y AST: cuando los nodos del árbol se referencian entre sí.
  • \n
  • Motores de juegos: entidades que contienen referencias a sus componentes.
  • \n
  • Protocolos de red: búferes con punteros internos.
  • \n
  • Sistemas de plugins: cuando un objeto contiene una referencia a su configuración.
  • \n
\n\n

¡La buena noticia es que hay formas seguras de sortear esta limitación!

\n\n

Solución 1: Usar índices en lugar de referencias (la forma más simple)

\n\n

La forma más simple y segura es almacenar no una referencia, sino un índice o una clave. En lugar de &str, almacena un usize (la posición del primer carácter).

\n\n
struct MyStruct {\n    data: String,\n    first_char_index: usize,  // almacenamos la posición, no la referencia\n}\n\nimpl MyStruct {\n    fn first_char(&self) -> &                

Blogs

Book Recommendations