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 ymem::align_of::<i32>()para su alineación.mem::size_of_val(&x)se emplea para determinar el tamaño de la instanciax. - Intercambio y Reemplazo:
mem::swap(&mut a, &mut b)intercambia los contenidos de las estructurasaybde forma atómica para el borrow checker.mem::replace(&mut a, Data { value: 100 })es crucial porque permite obtener la propiedad del valor original dea(elviejo) mientras asignamos un nuevoDataa la referencia mutua, evitando dejar la memoria deaen un estado inválido. - Take:
mem::take(&mut c)utiliza la implementación deDefaultdeDatapara dejar acen 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 yunsafe { uninit_val.assume_init() }para convertirlo en unu32legítimo. - Transmute:
transmute(bits_u32)realiza una reinterpretación de bits de unu32a un array[u8; 4]. Es una operaciónunsafeporque el programador debe garantizar que la representación de bits sea válida para el tipo de destino. - Forget:
mem::forget(tracker)consume la variabletrackersin llamar a su implementación deDrop, lo que en este caso evita la impresión del mensaje dentro dedrop().
N° 120