En Rust, la palabra clave unsafe no desactiva el verificador de préstamos (borrow checker), sino que permite al programador realizar operaciones que el compilador no puede validar de forma segura. El uso de unsafe se divide principalmente en tres áreas: funciones, rasgos (traits) y tipos.

unsafe fn se utiliza cuando una función realiza operaciones intrínsecamente peligrosas, como la desreferenciación de punteros crudos. En estos casos, la función misma puede ser marcada como unsafe, delegando la responsabilidad de mantener las invariantes (como que un puntero no sea nulo o esté alineado) al llamador. Es una práctica estándar de ingeniería documentar estas condiciones mediante la sección # Safety en la documentación del ítem.

Los unsafe trait elevan la responsabilidad al nivel de la implementación. Si un programador implementa un unsafe trait para un tipo, debe garantizar que la implementación respete las reglas de seguridad del trait (por ejemplo, que el tipo sea seguro de enviar entre hilos en el caso de Send). Si se implementa incorrectamente, se rompe la seguridad de memoria de todo el programa.

Finalmente, las union son tipos donde todos sus campos comparten la misma ubicación de memoria, de forma similar a las estructuras de C. Dado que el compilador no puede determinar qué variante de la unión es la que está activa en un momento dado, el acceso a cualquier campo de una union es siempre una operación unsafe, ya que interpretar un tipo de dato como otro (por ejemplo, leer un f64 como u64) puede llevar a comportamiento indefinido si no se gestiona con cuidado.

// Union: todos los campos comparten la misma ubicación de memoria
union DataUnion {
    f64_val(f64),
    u64_val(u64),
}

// Un unsafe trait: su implementación requiere garantías del programador
unsafe trait Validated {
    // El método es unsafe para obligar al llamador a verificar la integridad
    unsafe fn check(&self) -> bool;
}

struct SafeContainer(u64);

// Implementación de un unsafe trait
unsafe impl Validated for SafeContainer {
    // # Safety: El programador garantiza que el valor internamente es > 0
    unsafe fn check(&self) -> bool {
        self.0 > 0
    }
}

// unsafe fn: requiere que el puntero `ptr` sea válido y esté alineado
// # Safety: El puntero `ptr` debe ser un puntero válido para escrituras.
unsafe fn set_value(ptr: *mut u64, val: u64) {
    *ptr = val;
}

fn main() {
    // 1. Uso de union
    let mut u = DataUnion { u64_val: 100 };
    unsafe {
        // El acceso a campos de una union es siempre inseguro
        println!("Valor u64: {}", u.u64_val);
        // Interpretamos los mismos bits como f64
        println!("Valor f64 (reinterpretado): {}", u.f64_val);
    }

    // 2. Uso de unsafe trait
    let container = SafeContainer(50);
    // El método `check` es unsafe, requiere un bloque unsafe
    let es_valido = unsafe { container.check() };
    if es_valido {
        println!("El contenedor es válido.");
    }

    // 3. Uso de unsafe fn
    let mut number: u64 = 0;
    let ptr = &mut number as *mut u64;
    
    unsafe {
        // Llamar a `set_value` requiere un bloque unsafe
        set_value(ptr, 42);
    }
    println!("Número actualizado: {}", number);
}

Explicación del Código

  • union DataUnion: Define un tipo donde f64_val y u64_val comparten la misma dirección de memoria.
  • unsafe trait Validated: Define un rasgo que requiere que la implementación garantice la validez de los datos.
  • unsafe impl Validated for SafeContainer: El implementador asume la responsabilidad de que la lógica de check sea segura.
  • unsafe fn set_value(ptr: *mut u64, val: u64): Esta función es marcada como unsafe porque desreferencia un puntero crudo (*ptr = val), lo cual es una operación prohibida en Rust seguro.
  • u.u64_val y u.f64_val: El acceso a estos campos dentro de main requiere un bloque unsafe porque el compilador no puede asegurar qué tipo de dato se almacenó realmente en la DataUnion.
  • container.check(): Dado que el método check dentro del trait Validated fue definido como unsafe fn, su invocación en main debe estar envuelta en un bloque unsafe.
  • set_value(ptr, 42): La llamada a esta función en main requiere un bloque unsafe debido a la naturaleza de la definición de la función.

96