En Rust, la seguridad de memoria es la prioridad absoluta, pero el lenguaje reconoce que para interactuar con hardware, optimizar el rendimiento al extremo o implementar ciertas abstracciones de bajo nivel, el compilador necesita la ayuda del programador. Aquí es donde entra el bloque unsafe.
El uso de unsafe no desactiva el borrow checker ni las reglas de propiedad; lo que hace es permitir cinco operaciones específicas que el compilador no puede garantizar como seguras mediante el sistema de tipos. Al entrar en un bloque unsafe, el programador asume la responsabilidad de garantizar que no se produzcan condiciones de carrera, accesos a memoria inválida o punteros nulos. El objetivo principal de unsafe no es “saltarse las reglas”, sino minimizar la superficie de código que debe ser auditada manualmente; una buena práctica de ingeniería es encapsular este código peligroso dentro de APIs seguras (safe), de modo que el resto de la aplicación pueda usar la funcionalidad sin necesidad de usar unsafe.
Las cinco operaciones que solo pueden ejecutarse dentro de un contexto unsafe son:
- Desreferenciar punteros crudos (raw pointers): A diferencia de las referencias (
&To&mut T), los punteros crudos (*const To*mut T) pueden ser nulos, apuntar a memoria que ya ha sido liberada o no estar alineados. - Llamar funciones
unsafe: Funciones que dependen de condiciones que el compilador no puede verificar (como la validez de un puntero pasado como argumento). - Acceder o modificar variables
static mut: Las variables estáticas mutables son globales y, por tanto, susceptibles a condiciones de carrera si varios hilos intentan acceder a ellas simultáneamente. - Implementar traits
unsafe: Algunos traits imponen requisitos de seguridad que el compilador no puede verificar, como asegurar que un tipo sea seguro de compartir entre hilos (Sync). - Acceder a los campos de una
union: Debido a que unaunionalmacena diferentes tipos en la misma ubicación de memoria, el compilador no puede saber qué tipo de dato es el que realmente reside allí en un momento dado.
// Operación 3: Acceso a una variable static mut
static mut GLOBAL_COUNTER: u32 = 0;
// Operación 2: Definición de una función unsafe
// Esta función requiere un puntero crudo para funcionar.
unsafe fn increment_via_pointer(ptr: *mut u32) {
// Operación 1: Desreferenciar un puntero crudo
// El programador garantiza que ptr no es nulo y es válido.
if !ptr.is_null() {
*ptr += 1;
}
}
fn main() {
let mut local_value: u32 = 10;
// Creamos punteros crudos a partir de una referencia segura
let raw_ptr_local: *mut u32 = &mut local_value as *mut u32;
let raw_ptr_global: *mut u32 = &mut GLOBAL_COUNTER as *mut u32;
unsafe {
// Operación 3: Modificar static mut
GLOBAL_COUNTER += 1;
// Operación 1: Desreferenciar el puntero local
*raw_ptr_local += 5;
// Operación 2: Llamar a una función marcada como unsafe
increment_via_pointer(raw_ptr_local);
increment_via_pointer(raw_ptr_global);
println!("Valor local: {}, Counter global: {}", local_value, GLOBAL_COUNTER);
}
}
Explicación del Código
static mut GLOBAL_COUNTER: u32: Se declara una variable estática mutable que reside en el segmento de datos del binario. Su acceso es intrínsecamente peligroso.unsafe fn increment_via_pointer(ptr: *mut u32): La función está marcada conunsafeporque su comportamiento depende de que el argumentoptrsea un puntero válido y no nulo, algo que el compilador no puede asegurar.raw_ptr_local: *mut u32: Se crea un puntero crudo mediante la conversión (casting) de una referencia mutable segura&mut local_value.unsafe { ... }: El bloque delimita el área de riesgo. Dentro de él, se realizan las operaciones prohibidas en código seguro.GLOBAL_COUNTER += 1: Operación de modificación de una variablestatic mutdentro del bloqueunsafe.*raw_ptr_local += 5: Desreferenciación del punteroraw_ptr_localpara modificar el valor delocal_value.increment_via_pointer(raw_ptr_local): Invocación de la funciónunsafepasando el puntero crudo.
N° 94