· 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:

  1. Desreferenciar un puntero crudo.
  2. Llamar a una función o método unsafe.
  3. Acceder a una variable estática mutable o modificarla.
  4. Implementar un trait unsafe.
  5. 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.

Tu códigoInterfaz segura: ultimo(&lista)1. Comprueba que la lista no esté vacíaunsafe2. Lee sin comprobar de nuevoSAFETY: la condición ya se verificó
Quien llama no puede provocar el error: el riesgo vive en un solo lugar, marcado y documentado.

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 SAFETY que 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

  1. The Rust Programming Language: Unsafe RustThe Rust Project
  2. The Rustonomicon: Meet Safe and UnsafeThe Rust Project
  3. Rust in Android: move fast and fix thingsGoogle, 2025
  4. Miri: an interpreter for Rust's mid-level intermediate representationThe Rust Project
  5. Clippy lint: undocumented_unsafe_blocksThe Rust Project

Construye mejor software.

Seguridad, eficiencia y oficio, con fuentes y en español.