PhantomData<T> es un tipo de tamaño cero (Zero-Sized Type o ZST) que se utiliza como un marcador para informar al compilador de Rust sobre la relación de propiedad o la varianza de un tipo genérico T que no es explícitamente visible en la estructura. Esto es fundamental cuando se trabaja con punteros crudos (*const T o *mut T), ya que el compilador no puede inferir automáticamente si la estructura es dueña de los datos, si debe considerar el tipo como Send/Sync, o cómo debe manejarse la varianza de los lifetimes. Sin PhantomData<T>, el drop checker podría permitir que un tipo que “debería” ser invalidado permanezca vivo, o el compilador podría ignorar las restricciones de seguridad de hilos.

Los Tipos de Tamaño Dinámico (DST), también conocidos como tipos unsized, son aquellos cuyo tamaño no se conoce en tiempo de compilación, como str (cadenas literales) o [T] (slices). Debido a su naturaleza, los DST no pueden almacenarse directamente en el stack ni ser el argumento de una función de forma convencional; solo pueden ser utilizados mediante “punteros gordos” (fat pointers). Un fat pointer es una estructura que contiene el puntero a la dirección de memoria más metadatos adicionales (como la longitud de un slice o la vtable de un dyn Trait). En Rust, la mayoría de los tipos son Sized por defecto; para permitir que una función sea genérica sobre un tipo que podría ser un DST, se debe utilizar el bound ?Sized (que significa “puede o no ser Sized“).

use std::marker::PhantomData;
use std::mem::size_of;

// Una estructura que envuelve un puntero crudo para simular propiedad.
struct SafeWrapper<T> {
    ptr: *const T,
    // PhantomData<T> le indica al compilador que SafeWrapper "posee" un T.
    // Sin esto, el compilador no sabría si T debe ser validado por el 
    // drop checker o si la estructura debe implementar Send/Sync basándose en T.
    _marker: PhantomData<T>,
}

impl<T> SafeWrapper<T> {
    fn new(ptr: *const T) -> Self {
        Self {
            ptr,
            _marker: PhantomData,
        }
    }
}

// Función genérica que acepta tanto tipos Sized como DSTs (gracias a ?Sized).
fn analizar_tipo<T: ?Sized>(dato: &T) {
    println!("Tamaño de T en memoria: {} bytes", size_of::<T>());
}

fn main() {
    let valor_u32 = 42u32;
    let wrapper = SafeWrapper::new(&valor_u32 as *const u32);

    // 1. Demostración de PhantomData: SafeWrapper no ocupa espacio extra por _marker.
    println!("Tamaño de SafeWrapper<u32>: {} bytes", size_of::<SafeWrapper<u32>>());

    // 2. Demostración de DST (Dynamic Sized Types)
    // El tipo [u32] es un DST (su tamaño depende de cuántos elementos tenga).
    let array: [u32; 3] = [1, 2, 3];
    
    // 'slice' es un fat pointer: contiene el puntero al inicio y la longitud (3).
    let slice: &[u32] = &array[..]; 

    // El tipo str es un DST; solo podemos referenciarlo mediante &str.
    let texto: &str = "Rust Avanzado";

    // Llamadas a la función genérica
    analizar_tipo(&valor_u32); // T es u32 (Sized)
    analizar_tipo(slice);      // T es [u32] (DST)
    analizar_tipo(texto);      // T es str (DST)
}

Explicación del Código

  • SafeWrapper<T>: Esta estructura utiliza un puntero crudo ptr de tipo *const T. Como los punteros crudos no implican propiedad, se añade _marker: PhantomData<T> para que el compilador trate a SafeWrapper<T> como si contuviera un T, afectando la varianza y el drop checker.
  • size_of::<SafeWrapper<u32>>(): Devuelve el tamaño en bytes de la estructura. El resultado es el tamaño de un puntero, confirmando que PhantomData<T> es un ZST y no ocupa memoria adicional.
  • analizar_tipo<T: ?Sized>(dato: &T): El bound T: ?Sized es crucial. Si no se incluyera, el compilador asumiría que T: Sized por defecto, impidiendo que la función acepte tipos como str o [u32].
  • slice: &[u32]: Es un fat pointer. El tipo T en la función se vuelve [u32]. Aunque [u32] es un DST, la función recibe una referencia &T, lo cual es legal.
  • texto: &str: Aquí T es str. Al ser un tipo de tamaño dinámico, no podemos tener una variable de tipo str directamente, solo una referencia &str que contiene la dirección y la longitud del string.
  • size_of::<T>(): Función de la biblioteca estándar utilizada para inspeccionar el tamaño en tiempo de compilación de los tipos pasados.

104