El módulo std::mem proporciona las herramientas esenciales para manipular la memoria de forma directa, permitiendo inspeccionar el diseño (layout) de los tipos o gestionar la propiedad (ownership) de los valores en situaciones donde el sistema de tipos de Rust es restrictivo. Estas funciones son fundamentales tanto para la optimización de alto rendimiento como para la interoperabilidad con lenguajes como C (FFI) y la creación de estructuras de datos de ultra bajo nivel.

El uso de std::mem se divide en tres categorías críticas. Primero, la introspección de layout, mediante size_of (tamaño en bytes en tiempo de compilación) y align_of (alineación de memoria requerida), junto a size_of_val para tipos con tamaño dinámico (DSTs). Segundo, la gestión de propiedad y movimiento, donde swap permite intercambiar valores sin violar las reglas de movimiento, y replace o take permiten extraer un valor de una referencia mutable (&mut T) reemplazándolo por otro, evitando dejar la memoria en un estado inválido. Sin estas, sería imposible implementar estructuras como Vec o String de forma eficiente.

Tercero, la manipulación insegura y de ciclo de vida. mem::forget permite que un valor escape del sistema de propiedad de Rust, impidiendo que se ejecute su Drop (esencial en FFI). MaybeUninit<T> es la herramienta moderna para manejar memoria no inicializada de forma segura, evitando el uso de la hoy obsoleta mem::uninitialized. Finalmente, transmute es la operación más peligrosa: reinterpreta los bits de un tipo A como si fueran un tipo B. Si los tamaños o representaciones no coinciden perfectamente, se produce Comportamiento Indefinido (UB).

use std::mem::{self, MaybeUninit, transmute};

#[derive(Debug, Default)]
struct Data {
    value: i32,
}

fn main() {
    // 1. Introspección de memoria
    println!("--- Layout ---");
    println!("Tamaño de i32: {} bytes", mem::size_of::<i32>());
    println!("Alineación de i32: {} bytes", mem::align_of::<i32>());
    
    let x = 10;
    let y = 20;
    println!("Tamaño de val (x): {} bytes", mem::size_of_val(&x));

    // 2. Manipulación de valores y ownership
    println!("\n--- Ownership ---");
    let mut a = Data { value: 1 };
    let mut b = Data { value: 2 };
    
    // swap intercambia contenidos sin mover la propiedad
    mem::swap(&mut a, &mut b);
    println!("Swap (a, b): a={:?}, b={:?}", a, b);

    // replace extrae el valor viejo y pone uno nuevo
    let viejo = mem::replace(&mut a, Data { value: 100 });
    println!("Replace (a con 100): a={:?}, viejo={:?}", a, viejo);

    // take extrae el valor y reemplaza con Default
    let mut c = Data { value: 50 };
    let valor_c = mem::take(&mut c);
    println!("Take (c): c={:?}, extraído={:?}", c, valor_c);

    // 3. Memoria no inicializada y reinterpretación
    println!("\n--- Low Level ---");
    // MaybeUninit para evitar lectura de basura antes de inicializar
    let mut uninit_val = MaybeUninit::<u32>::uninit();
    uninit_val.write(0xDEADBEEF);
    let valor_seguro = unsafe { uninit_val.assume_init() };
    println!("MaybeUninit: {:#X}", valor_seguro);

    // transmute: reinterpretación de bits (u32 -> [u8; 4])
    let bits_u32: u32 = 0x12345678;
    let bytes: [u8; 4] = unsafe { transmute(bits_u32) };
    println!("Transmute (u32 -> [u8; 4]): {:?}", bytes);

    // 4. Forget: el valor no llamará a Drop
    struct DropChecker(i32);
    impl Drop for DropChecker {
        fn drop(&mut self) { println!("Esto NO se imprimirá si usamos forget"); }
    }
    let tracker = DropChecker(42);
    mem::forget(tracker);
}

Explicación del Código

  • Introspección: Se utiliza mem::size_of::<i32>() para obtener el tamaño estático de un tipo y mem::align_of::<i32>() para su alineación. mem::size_of_val(&x) se emplea para determinar el tamaño de la instancia x.
  • Intercambio y Reemplazo: mem::swap(&mut a, &mut b) intercambia los contenidos de las estructuras a y b de forma atómica para el borrow checker. mem::replace(&mut a, Data { value: 100 }) es crucial porque permite obtener la propiedad del valor original de a (el viejo) mientras asignamos un nuevo Data a la referencia mutua, evitando dejar la memoria de a en un estado inválido.
  • Take: mem::take(&mut c) utiliza la implementación de Default de Data para dejar a c en su estado por defecto y devolver su contenido original (valor_c).
  • Memoria no inicializada: MaybeUninit::<u32>::uninit() reserva espacio en el stack sin inicializar. Para usarlo de forma segura, se usa .write() para asignar un valor y unsafe { uninit_val.assume_init() } para convertirlo en un u32 legítimo.
  • Transmute: transmute(bits_u32) realiza una reinterpretación de bits de un u32 a un array [u8; 4]. Es una operación unsafe porque el programador debe garantizar que la representación de bits sea válida para el tipo de destino.
  • Forget: mem::forget(tracker) consume la variable tracker sin llamar a su implementación de Drop, lo que en este caso evita la impresión del mensaje dentro de drop().

120