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 dondef64_valyu64_valcomparten 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 dechecksea segura.unsafe fn set_value(ptr: *mut u64, val: u64): Esta función es marcada comounsafeporque desreferencia un puntero crudo (*ptr = val), lo cual es una operación prohibida en Rust seguro.u.u64_valyu.f64_val: El acceso a estos campos dentro demainrequiere un bloqueunsafeporque el compilador no puede asegurar qué tipo de dato se almacenó realmente en laDataUnion.container.check(): Dado que el métodocheckdentro del traitValidatedfue definido comounsafe fn, su invocación enmaindebe estar envuelta en un bloqueunsafe.set_value(ptr, 42): La llamada a esta función enmainrequiere un bloqueunsafedebido a la naturaleza de la definición de la función.
N° 96