La Interoperabilidad con Lenguajes Extranjeros (FFI, por sus siglas en inglés) es la capacidad de Rust para comunicarse con código escrito en otros lenguajes, siendo C el estándar de facto gracias a su ABI (Application Binary Interface) estable y predecible. Para lograr esto, Rust utiliza el bloque extern "C", el cual indica al compilador que las funciones declaradas dentro de este bloque deben seguir la convención de llamada de C (cómo se pasan argumentos en registros o pila, y cómo se devuelve el valor).

Cuando llamamos a una función de C desde Rust, el compilador no puede garantizar que la función externa respete las reglas de seguridad de memoria de Rust (como la validez de los punteros). Por ello, todas las llamadas a funciones extern deben ejecutarse dentro de un bloque unsafe.

El proceso es bidireccional. Para que Rust sea “visible” para C, se utilizan dos herramientas críticas: #[no_mangle] y extern "C". Por defecto, Rust utiliza un proceso llamado name mangling para evitar colisiones de nombres, transformando el nombre de la función en una cadena compleja que incluye metadatos de módulos y tipos. Al aplicar #[no_mangle], le ordenamos al enlazador (linker) que mantenga el nombre exacto de la función en el símbolo binario, permitiendo que una biblioteca en C la localice.

Finalmente, la gestión de datos requiere precisión en la disposición de memoria. Rust no garantiza que un struct tenga el mismo orden de campos que en C. Para evitar este error, se utiliza el atributo #[repr(C)], que fuerza a Rust a organizar los campos en memoria siguiendo la especificación de C. Para asegurar la portabilidad de tipos básicos (como asegurar que un entero tenga el mismo tamaño en diferentes plataformas), se deben utilizar los tipos de std::os::raw, como c_int o c_char, en lugar de los tipos nativos de Rust.

use std::os::raw::{c_int, c_char};

// Declaración de funciones externas definidas en una biblioteca C (ej. libc).
// El bloque extern "C" establece la ABI necesaria para la comunicación.
extern "C" {
    fn abs(input: c_int) -> c_int;
}

// #[repr(C)] garantiza que la estructura tenga la misma disposición 
// de memoria que una struct de C, evitando errores de alineación.
#[repr(C)]
pub struct Point {
    pub x: c_int,
    pub y: c_int,
}

// Para exportar una función de Rust hacia C, usamos:
// 1. #[no_mangle]: Mantiene el nombre 'calculate_distance' sin alteraciones.
// 2. extern "C": Define la convención de llamada para el llamador de C.
#[no_mangle]
pub extern "C" fn calculate_distance(p1: *const Point, p2: *const Point) -> f64 {
    // La manipulación de punteros crudos es inherentemente insegura.
    if p1.is_null() || p2.is_null() {
        return 0.0;
    }

    unsafe {
        // Acceso a la memoria a través de punteros desreferenciados.
        let x1 = (*p1).x as f64;
        let y1 = (*p1).y as f64;
        let x2 = (*p2).x as f64;
        let y2 = (*p2).y as f64;

        ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
    }
}

fn main() {
    // Llamada a una función C externa.
    // El bloque unsafe es obligatorio porque el compilador no puede 
    // verificar la seguridad de la función 'abs'.
    let val: c_int = -42;
    let abs_val = unsafe { abs(val) };

    println!("El valor absoluto de {} es {} (desde C)", val, abs_val);

    let pt1 = Point { x: 0, y: 0 };
    let pt2 = Point { x: 3, y: 4 };

    // Llamada a nuestra función exportada.
    let dist = calculate_distance(&pt1, &pt2);
    println!("La distancia calculada es: {}", dist);
}

Explicación del Código


extern "C" { fn abs(...) }: Declara la interfaz hacia la biblioteca C. Indica que la función abs no reside en Rust.
unsafe { abs(val) }: El bloque unsafe es obligatorio porque Rust no puede asegurar que la función externa abs sea segura o no rompa el programa.
#[repr(C)] struct Point: Asegura que Point tenga un layout de memoria predecible (ej. x seguido de y sin padding inesperado) compatible con C.
#[no_mangle]: Elimina el name mangling de calculate_distance, permitiendo que un programa en C la llame usando ese nombre literal.
extern "C" fn calculate_distance(...): Define que la función de Rust utilizará la convención de llamada de C, permitiendo que C la invoque correctamente.
*const Point: Representa un puntero constante en C (Point *). Se usa para pasar la dirección de memoria de los structs.
c_int: Tipo portable de std::os::raw que garantiza que el tamaño del entero coincida con el de int en C en cualquier plataforma.
is_null(): Método utilizado para verificar que los punteros recibidos no sean nulos antes de intentar desreferenciarlos en el bloque unsafe.

97