Rust divide el código en dos categorías: seguro (safe) e inseguro (unsafe). El código safe es el que escribe el 99% del tiempo, con todas las garantías del compilador. El código unsafe no desactiva el compilador ni convierte Rust en C: solo habilita exactamente cinco operaciones adicionales que el código safe no puede hacer. Entender cuáles son y cuándo son necesarias es lo que separa el uso correcto del abuso.
Las cinco operaciones unsafe
- Desreferenciar raw pointers (
*const Ty*mut T). - Llamar a funciones
unsafe(incluidas funciones de C por FFI). - Acceder o modificar una variable estática mutable.
- Implementar un
unsafe trait. - Acceder a campos de una
union.
Raw pointers
fn main() {
let mut valor = 42i32;
// Crear raw pointers es safe; desreferenciarlos no
let ptr_inmutable: *const i32 = &valor;
let ptr_mutable: *mut i32 = &mut valor;
unsafe {
println!("Valor via ptr: {}", *ptr_inmutable);
*ptr_mutable = 100;
println!("Modificado: {}", *ptr_inmutable);
}
// Los raw pointers pueden ser nulos y apuntar a memoria inválida
// El compilador no lo comprueba: TÚ eres el responsable
let ptr_nulo: *const i32 = std::ptr::null();
// unsafe { println!("{}", *ptr_nulo); } // UB: puntero nulo
}
Funciones unsafe
// Declarar una función unsafe: quien la llame asume la responsabilidad
unsafe fn desplazar_puntero(ptr: *mut i32, offset: isize) -> *mut i32 {
ptr.offset(offset)
}
// Crear una abstracción safe sobre código unsafe
pub fn dividir_slice(slice: &[i32], indice: usize) -> (&[i32], &[i32]) {
assert!(indice <= slice.len());
let ptr = slice.as_ptr();
unsafe {
(
std::slice::from_raw_parts(ptr, indice),
std::slice::from_raw_parts(ptr.add(indice), slice.len() - indice),
)
}
}
fn main() {
let datos = [1, 2, 3, 4, 5];
let (izq, der) = dividir_slice(&datos, 3);
println!("{izq:?} | {der:?}"); // [1, 2, 3] | [4, 5]
}
FFI: llamar a funciones de C
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
extern "C" {
fn strlen(s: *const c_char) -> usize;
}
fn main() {
let cadena = CString::new("hola mundo").unwrap();
unsafe {
let longitud = strlen(cadena.as_ptr());
println!("strlen: {longitud}"); // 10
}
// Convertir *const c_char de C a &str de Rust
let ptr: *const c_char = cadena.as_ptr();
unsafe {
let s = CStr::from_ptr(ptr).to_str().unwrap();
println!("Desde C: {s}");
}
}
Static mutable: uso correcto
static mut CONTADOR: u32 = 0;
// INCORRECTO en código real: sin sincronización, UB en multihilo
unsafe fn incrementar() {
CONTADOR += 1;
}
// CORRECTO: usar AtomicU32 para statics mutables
use std::sync::atomic::{AtomicU32, Ordering};
static CONTADOR_ATOMICO: AtomicU32 = AtomicU32::new(0);
fn incrementar_safe() {
CONTADOR_ATOMICO.fetch_add(1, Ordering::Relaxed);
}
fn main() {
incrementar_safe();
incrementar_safe();
println!("{}", CONTADOR_ATOMICO.load(Ordering::Relaxed)); // 2
}
Miri: detectar UB en código unsafe
// Instalar y ejecutar Miri:
// rustup component add miri
// cargo miri test
// Miri detecta:
// - Desreferenciación de punteros inválidos o nulos
// - Acceso fuera de bounds
// - Uso de memoria sin inicializar
// - Data races (con -Zmiri-track-raw-pointers)
// Ejemplo de UB que Miri detecta:
unsafe fn mal_uso() {
let datos = vec![1, 2, 3];
let ptr = datos.as_ptr();
drop(datos); // libera la memoria
// println!("{}", *ptr); // use-after-free: Miri lo detecta
}
Cuándo (y cuándo no) usar unsafe
- Sí: implementar estructuras de datos que la stdlib no cubre (listas intrusivas, arenas).
- Sí: llamar a librerías C con bindings FFI.
- Sí: optimizaciones de bajo nivel donde el análisis manual garantiza la corrección.
- No: como forma de "saltarse" el borrow checker cuando el diseño puede corregirse.
- No: traducción literal de código C sin revisar los invariantes.
Resumen
- Un bloque
unsafehabilita exactamente cinco capacidades; el resto de las garantías del compilador permanecen intactas. - Los raw pointers se crean en código safe pero solo se desreferencian en
unsafe. - Una función
unsafeexige al caller que documente y cumpla los invariantes de seguridad. - Para statics mutables, prefiere
AtomicU32u otros tipos atómicos en lugar destatic mutdesnudo. - Usa Miri en tu suite de tests para detectar comportamiento indefinido en bloques unsafe.
