Los punteros crudos (raw pointers) son la forma más directa de manipular direcciones de memoria en Rust. Existen dos tipos: *const T (inmutable) y *mut T (mutable). A diferencia de las referencias (&T y &mut T), los punteros crudos son “tontos”: no poseen información de vida (lifetimes), no garantizan que la memoria sea válida y, lo más crítico, pueden ser nulos.
En Rust, la creación de un puntero crudo es una operación segura (safe). Puedes convertir una referencia en un puntero crudo simplemente mediante un cast: &val as *const T. Sin embargo, el uso de estos punteros es donde reside el peligro. La desreferenciación (acceder al valor al que apuntan) es una operación unsafe. Esto se debe a que el compilador no puede verificar si el puntero es válido, si está alineado correctamente en la memoria, si la memoria es propia del proceso o si estás violando las reglas de exclusividad (aliasing) al tener múltiples punteros mutables apuntando al mismo lugar.
Si se utilizan incorrectamente, se puede caer en Comportamiento Indefinido (Undefined Behavior), como el acceso a memoria ya liberada (use-after-free) o la corrupción de datos por condiciones de carrera. Los punteros crudos son esenciales para implementar estructuras de datos de bajo nivel (como Vec o HashMap), realizar optimizaciones extremas o implementar interfaces FFI (Foreign Function Interface) para interactuar con código en C/C++. Para casos donde se requiere un puntero que nunca sea nulo, se utiliza std::ptr::NonNull<T>, que permite al compilador realizar optimizaciones de nicho (como en Option<NonNull<T>>).
use std::ptr;
fn main() {
// 1. Creación de punteros: desde referencias y punteros nulos
let value: i32 = 42;
let ptr_const: *const i32 = &value as *const i32;
let ptr_mut: *mut i32 = &mut value as *mut i32;
let null_ptr: *mut i32 = ptr::null_mut();
println!("Const: {:?}, Mut: {:?}, Null: {:?}", ptr_const, ptr_mut, null_ptr);
// 2. Gestión de memoria manual con ptr::write y ptr::read
// Creamos un Box para tener una dirección de memoria en el heap
let mut boxed_val = Box::new(100);
let raw_ptr: *mut i32 = Box::into_raw(boxed_val);
unsafe {
// ptr::write escribe un valor en la dirección sin ejecutar Drop
// en el contenido previo, ideal para memoria no inicializada.
ptr::write(raw_ptr, 200);
assert_eq!(*raw_ptr, 200);
// ptr::read extrae el valor moviéndolo (move) fuera del puntero.
// El contenido en raw_ptr queda en un estado "no inicializado" legalmente.
let moved_val: i32 = ptr::read(raw_ptr);
assert_eq!(moved_val, 200);
// Para evitar un doble-free al finalizar el programa, volvemos a escribir
// el valor para que Box::from_raw pueda liberarlo de forma segura.
ptr::write(raw_ptr, moved_val);
}
// 3. Aritmética de punteros y copias de bloques
let mut src_data = vec![10, 20, 30, 40, 50];
let mut dest_data = vec![0, 0, 0, 0, 0];
let src_ptr: *const i32 = src_data.as_ptr();
let dst_ptr: *mut i32 = dest_data.as_mut_ptr();
unsafe {
// ptr::copy_nonoverlapping es el equivalente a memcpy de C.
// Se usa cuando estamos seguros de que los rangos de memoria no se solapan.
ptr::copy_nonoverlapping(src_ptr, dst_ptr, 3);
// ptr::copy es equivalente a memmove; seguro incluso con solapamiento.
ptr::copy(src_ptr, dst_ptr, 2);
}
assert_eq!(dest_data, vec![10, 20, 30, 40, 50]);
// Limpieza final del puntero del heap
unsafe {
let _ = Box::from_raw(raw_ptr);
}
}
Explicación del Código
ptr_const: *const i32yptr_mut: *mut i32: Se crean mediante el casting de referencias existentes.ptr_constes un puntero inmutable yptr_mutes mutable.null_ptr: *mut i32: Se utilizaptr::null_mut()para crear un puntero que apunta a la dirección0x0. Intentar desreferenciarlo causaría un error de segmentación.raw_ptr: *mut i32: Se obtiene medianteBox::into_raw(boxed_val). Esto transfiere la responsabilidad de la memoria al programador; elBoxoriginal ya no controla la vida de los datos en el heap.ptr::write(raw_ptr, 200): Esta función es crucial. A diferencia de*raw_ptr = 200,ptr::writeno intenta llamar al destructor (drop) del valor que residía en esa dirección. Es esencial cuando trabajas con memoria recién asignada que no ha sido inicializada.ptr::read(raw_ptr): Esta función realiza un “move” de los datos desde la dirección de memoria hacia la variablemoved_val. Después de esta llamada, el contenido enraw_ptrdebe considerarse inválido; no se debe volver a leer ni a liberar.ptr::copy_nonoverlapping(src_ptr, dst_ptr, 3): Esta operación de alto rendimiento copia 3 elementos desde la direcciónsrc_ptrhaciadst_ptr. Se asume que los buffers no se superponen para maximizar la velocidad.ptr::copy(src_ptr, dst_ptr, 2): Similar a la anterior, pero está diseñada para manejar casos donde el origen y el destino se solapan en memoria (como unmemmoveen C).Box::from_raw(raw_ptr): Se utiliza para recuperar la propiedad de la memoria gestionada manualmente y permitir que el sistema de gestión de memoria de Rust libere el heap automáticamente al final del scope.
N° 95