El problema central que da origen a Pin<P> es la autorreferencialidad en las estructuras de datos. En Rust, la suposición fundamental es que cualquier valor puede ser movido en la memoria (mediante una copia de sus bytes, como ocurre en una asignación o al pasar un argumento por valor). Sin embargo, cuando escribimos código asíncrono con async/await, el compilador transforma nuestras funciones en máquinas de estado representadas como estructuras (structs).

Si una tarea asíncrona guarda una referencia local a una variable dentro de sí misma (por ejemplo, un &str que apunta a una String definida en la misma función), esa estructura resultante es “autorreferencial”. Si esta estructura se mueve de su ubicación original en el stack hacia el heap, o de una posición en el stack a otra, el puntero interno seguirá apuntando a la dirección de memoria vieja, que ahora es inválida. Esto resulta en un puntero colgante (dangling pointer) y comportamiento indefinido.

Pin<P> es un “wrapper” que envuelve un puntero (como &mut T) y garantiza que el valor al que apunta no podrá ser movido de su dirección de memoria mientras ese Pin exista. Es, esencialmente, una promesa de inmovilidad. Para que esto sea útil, Rust distingue entre tipos que son seguros de mover (que implementan el trait Unpin) y aquellos que no lo son. La mayoría de los tipos (como i32 o String) son Unpin, por lo que Pin<&mut T> se comporta como un simple &mut T. Sin embargo, los futuros generados por async fn son !Unpin (no implementan Unpin) precisamente porque pueden contener estas referencias internas peligrosas. Por esta razón, el método poll de la trait Future recibe Pin<&mut Self> en lugar de un simple &mut Self: esto obliga al implementador a garantizar que el futuro no se mueva una vez que se ha comenzado a procesar.

use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};

// Un tipo normal que implementa Unpin automáticamente.
// Es seguro moverlo de un lugar a otro.
struct Simple(u32);

// Un tipo que simula una estructura autorreferencial (!Unpin).
// Si movemos este objeto, 'ptr' dejará de apuntar a 'data'.
struct SelfReferential {
    data: u32,
    ptr: *const u32,
}

impl SelfReferential {
    fn new(val: u32) -> Self {
        let mut s = Self { data: val, ptr: std::ptr::null() };
        s.ptr = &s.data; // La referencia apunta a un campo interno
        s
    }
}

// Implementamos Future para entender la necesidad de Pin en el método poll.
impl Future for SelfReferential {
    type Output = u32;

    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
        // Para acceder a los campos de un tipo !Unpin, necesitamos "Pin Projection".
        // Usamos unsafe para obtener un &mut Self sin mover el objeto.
        let this = unsafe { self.get_unchecked_mut() };
        
        // Verificamos si la autorreferencia sigue siendo válida.
        if this.ptr == &this.data {
            Poll::Ready(this.data)
        } else {
            panic!("Error de memoria: El objeto se movió y la referencia es inválida!");
        }
    }
}

fn main() {
    // 1. Tipos Unpin: Se pueden pinear directamente con Pin::new.
    let mut s = Simple(10);
    let mut p_s: Pin<&mut Simple> = Pin::new(&mut s);
    println!("Simple: {}", p_s.0);

    // 2. Tipos !Unpin: Pin::new(sr) fallaría en compilación.
    let sr = SelfReferential::new(42);

    // 3. Pinning en el Heap con Box::pin.
    // Al mover el valor al heap, su dirección de memoria es estable.
    // Box<T> es Unpin, pero el contenido (SelfReferential) permanece fijo en el heap.
    let mut pinned_heap = Box::pin(SelfReferential::new(100));

    // 4. Pinning en el Stack con la macro std::pin::pin!
    // Esta macro asegura que el futuro se mantenga en una posición fija del stack.
    let mut pinned_stack = std::pin::pin!(async {
        let x = 5;
        let y = &x; // Referencia local (autorreferencial en el bloque async)
        println!("Stack async - x: {}, y: {}", x, y);
        *y
    });

    // Mock de Waker para poder ejecutar el poll manualmente en este ejemplo.
    let waker = unsafe { Waker::from_raw(create_raw_waker()) };
    let mut cx = Context::from_waker(&waker);

    // Ejecución de los futuros.
    let _ = pinned_stack.as_mut().poll(&mut cx);
    let _ = pinned_heap.as_mut().poll(&mut cx);
}

// Funciones auxiliares para crear un Waker vacío y permitir la compilación.
fn create_raw_waker() -> RawWaker {
    fn no_op(_: *const ()) {}
    fn clone(_: *const ()) -> RawWaker { create_raw_waker() }
    let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op);
    RawWaker::new(std::ptr::null(), vtable)
}

Explicación del Código

  • struct Simple(u32): Es un tipo que implementa automáticamente el trait Unpin, lo que significa que es seguro moverlo incluso si está envuelto en un Pin.
  • struct SelfReferential: Representa un tipo !Unpin. Contiene el campo data y el campo ptr, que es un puntero que apunta a data. Si este struct se mueve de dirección, ptr quedará apuntando a basura.
  • impl Future for SelfReferential: Aquí vemos por qué poll requiere Pin<&mut Self>. Si poll aceptara &mut Self, un usuario podría usar std::mem::swap para mover el objeto entre llamadas a poll, corrompiendo el puntero ptr.
  • self.get_unchecked_mut(): Dentro de poll, usamos este método unsafe para realizar “Pin Projection”. Esto nos permite obtener un &mut SelfReferential para leer sus campos sin violar la garantía de inmovilidad.
  • Box::pin(SelfReferential::new(100)): Esta es la forma de “pinear” en el heap. Box::pin mueve el objeto al heap y nos devuelve un Pin<Box<T>>. Aunque el Box en el stack se mueva, el contenido en el heap permanece en la misma dirección de memoria.
  • std::pin::pin!(async { ... }): Esta macro de la librería estándar realiza un pinning en el stack. Es extremadamente eficiente ya que no requiere una asignación en el heap, permitiendo que las variables locales de un bloque async sean seguras.
  • pinned_stack.as_mut().poll(&mut cx): Dado que pinned_stack es un Pin<&mut T>, debemos usar as_mut() para obtener la referencia necesaria para invocar el método poll de la trait Future.

86