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:

  1. Desreferenciar punteros crudos (raw pointers): A diferencia de las referencias (&T o &mut T), los punteros crudos (*const T o *mut T) pueden ser nulos, apuntar a memoria que ya ha sido liberada o no estar alineados.
  2. Llamar funciones unsafe: Funciones que dependen de condiciones que el compilador no puede verificar (como la validez de un puntero pasado como argumento).
  3. 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.
  4. 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).
  5. Acceder a los campos de una union: Debido a que una union almacena 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 con unsafe porque su comportamiento depende de que el argumento ptr sea 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 variable static mut dentro del bloque unsafe.
  • *raw_ptr_local += 5: Desreferenciación del puntero raw_ptr_local para modificar el valor de local_value.
  • increment_via_pointer(raw_ptr_local): Invocación de la función unsafe pasando el puntero crudo.

94