· 6 min de lectura · Por Grover Cordova
unsafe en Rust: qué es, cuándo usarlo y cómo auditarlo
unsafe no apaga el compilador. Qué permite realmente, cuándo se justifica, cómo aislarlo en una abstracción segura y cómo auditarlo con Miri.
Cuando alguien dice que Rust es seguro, la respuesta típica es: "pero tiene unsafe". Es cierto. Y entender qué significa es la diferencia entre confiar a ciegas y confiar con criterio.
Por qué existe
El compilador de Rust es conservador. Rechaza todo lo que no puede demostrar que es correcto, incluso programas correctos. Y hay trabajo que por naturaleza no puede comprobar: hablar con el hardware, llamar a código en C o construir las propias estructuras de la biblioteca estándar.
Sin unsafe, Rust no podría hacer eso. Vec, por ejemplo, gestiona memoria manualmente por dentro, y te ofrece por fuera una interfaz segura.
Qué permite de verdad
Aquí está el malentendido más común. unsafe no apaga el verificador de préstamos ni el resto de comprobaciones. Solo da acceso a cinco operaciones:
- Desreferenciar un puntero crudo.
- Llamar a una función o método
unsafe. - Acceder a una variable estática mutable o modificarla.
- Implementar un trait
unsafe. - Acceder a los campos de una
union.
Todo lo demás se sigue comprobando dentro del bloque. Y fuera de él, si solo escribes Rust seguro, no deberías encontrar comportamiento indefinido.
La idea central: abstracciones seguras
La regla de oro es encerrar unsafe en una función pequeña y exponer una interfaz segura. Quien la usa no puede provocar un error, y la responsabilidad queda en un solo lugar.
Este ejemplo devuelve el último elemento de una lista. Comprueba la condición antes de entrar en la zona unsafe:
/// Devuelve el último elemento, si existe.
fn ultimo(lista: &[i32]) -> Option<i32> {
if lista.is_empty() {
return None;
}
// SAFETY: `lista` no está vacía, de modo que `len() - 1`
// es un índice válido.
Some(unsafe { *lista.get_unchecked(lista.len() - 1) })
}
fn main() {
println!("{:?}", ultimo(&[1, 2, 3])); // Some(3)
println!("{:?}", ultimo(&[])); // None
}
Fíjate en el comentario SAFETY. Explica por qué la operación es correcta. Es una convención muy extendida, y Clippy puede exigirla con undocumented_unsafe_blocks.
Por cierto, en un caso real bastaría con lista.last().copied(). Es un ejemplo para mostrar la forma, y demuestra lo primero que debes preguntarte: ¿de verdad lo necesitas?
Cuándo se justifica
Sí suele estar justificado en:
- Interoperabilidad: llamar a bibliotecas en C o C++, como vimos en la guía de adopción en C++.
- Hardware: escribir registros en un microcontrolador o un driver.
- Estructuras de datos a bajo nivel: cuando la biblioteca estándar no cubre tu caso.
- Rendimiento comprobado: solo si una medición muestra que importa.
No se justifica para:
- Callar al compilador. Si el verificador se queja, casi siempre tiene razón. Es el error típico de quien empieza, y de los asistentes de IA.
- Optimizar por intuición. El compilador suele eliminar las comprobaciones de límites cuando puede demostrar que sobran.
- Evitar diseñar bien. Dos referencias mutables al mismo dato son un problema de diseño.
El caso de Android: un recordatorio útil
unsafe puede fallar. Google documentó el caso de CVE-2025-48530, un fallo en código unsafe de CrabbyAVIF, una biblioteca de Android para imágenes AVIF. Podría haber sido grave, pero se detectó antes de publicarse y un componente adicional, el asignador Scudo, lo habría hecho inexplotable.
Son dos lecciones a la vez. Rust no es una garantía absoluta, y por eso sigue haciendo falta defensa en profundidad. Pero los fallos se concentran en zonas pequeñas y conocidas, en lugar de repartirse por toda la base de código.
Cómo auditar unsafe
Si heredas un proyecto o evalúas una dependencia, este es un proceso razonable.
1. Localízalo. Es una de las grandes ventajas de Rust: el riesgo está marcado.
grep -rn "unsafe" src/
2. Prohíbelo donde no haga falta. Con #![forbid(unsafe_code)] el compilador rechaza cualquier bloque unsafe. Úsalo en todos los módulos que no lo necesiten.
3. Pásalo por Miri. Miri es la herramienta oficial que ejecuta tus pruebas e identifica comportamiento indefinido.
rustup +nightly component add miri
cargo +nightly miri test
Solo detecta lo que tus pruebas ejercitan, de modo que no reemplaza una revisión.
4. Revisa cada bloque con estas preguntas:
- ¿Hay un comentario
SAFETYque explique por qué es correcto? - ¿Las condiciones se comprueban antes, o dependen de quien llama?
- ¿Puede una entrada externa romper esas condiciones?
- ¿Hay una alternativa segura de costo similar?
- ¿La función pública es segura sin importar cómo se use?
5. Revisa tus dependencias. Tu proyecto puede no tener unsafe, y aun así heredarlo de otras bibliotecas. Herramientas como cargo audit y cargo deny ayudan a vigilar lo que entra.
Lo que debes recordar
Cuando alguien te diga que Rust no sirve porque tiene unsafe, recuerda la alternativa. En C, todo el programa es unsafe, sin marcar. En Rust, la zona peligrosa tiene nombre, se puede buscar con grep y cabe en una revisión.
Eso no elimina el riesgo. Lo vuelve manejable.
Fuentes
- The Rust Programming Language: Unsafe RustThe Rust Project
- The Rustonomicon: Meet Safe and UnsafeThe Rust Project
- Rust in Android: move fast and fix thingsGoogle, 2025
- Miri: an interpreter for Rust's mid-level intermediate representationThe Rust Project
- Clippy lint: undocumented_unsafe_blocksThe Rust Project