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 crudoptrde tipo*const T. Como los punteros crudos no implican propiedad, se añade_marker: PhantomData<T>para que el compilador trate aSafeWrapper<T>como si contuviera unT, 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 quePhantomData<T>es un ZST y no ocupa memoria adicional.analizar_tipo<T: ?Sized>(dato: &T): El boundT: ?Sizedes crucial. Si no se incluyera, el compilador asumiría queT: Sizedpor defecto, impidiendo que la función acepte tipos comostro[u32].slice: &[u32]: Es un fat pointer. El tipoTen 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íTesstr. Al ser un tipo de tamaño dinámico, no podemos tener una variable de tipostrdirectamente, solo una referencia&strque 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.
N° 104