Índice

FFI — Llamar código C

¿Por qué llamar a C?

Nyx es poderoso, pero el mundo funciona con C. Sistemas operativos, bibliotecas gráficas, bases de datos, algoritmos de compresión — décadas de código C probado en batalla existen. En lugar de reescribir todo, Nyx te permite llamar funciones C directamente. Esto se llama FFI — Interfaz de Funciones Foráneas.

Declarar una función externa

Para llamar una función C desde Nyx, la declaras con extern "C":

extern "C" fn abs(x: int) -> int

fn main() {
    print(abs(-42))    // 42
    print(abs(10))     // 10
}

Esto le dice al compilador: "hay una función C llamada abs que recibe un int y devuelve un int. La proporcionaré en tiempo de enlace."

La biblioteca estándar de C se enlaza automáticamente, así que funciones como abs simplemente funcionan.

Mapeo de tipos

Cuando llamas a C, los tipos deben coincidir. Así es como los tipos de Nyx se mapean a C:

Tipo Nyx Tipo C LLVM IR
int long long (64-bit) i64
float double (64-bit) double
bool _Bool i1
String const char* i8*
*int int64_t* i64*
i32 int (32-bit) i32
i8 char (8-bit) i8

Cuando pasas un String de Nyx a una función C, el compilador lo convierte automáticamente a un char* terminado en null.

Llamar funciones matemáticas

extern "C" fn sqrt(x: float) -> float
extern "C" fn pow(base: float, exp: float) -> float
extern "C" fn log(x: float) -> float

fn main() {
    let raiz: float = sqrt(144.0)
    print(raiz)    // 12.0

    let resultado: float = pow(2.0, 10.0)
    print(resultado)    // 1024.0
}

Enlaza con -lm (la biblioteca matemática) al compilar.

Llamar funciones de strings

extern "C" fn strlen(s: String) -> int
extern "C" fn strcmp(a: String, b: String) -> int

fn main() {
    print(strlen("Hello"))     // 5

    let cmp: int = strcmp("apple", "banana")
    if cmp < 0 {
        print("apple va primero")
    }
}

Structs compatibles con C

Para pasar structs a C, deben tener layout de memoria compatible con C. Usa #[repr(C)]:

#[repr(C)]
struct Punto {
    x: i64,
    y: i64
}

extern "C" fn procesar_punto(p: *Punto) -> int

fn main() {
    var p: Punto = Punto { x: 10, y: 20 }
    unsafe {
        let resultado: int = procesar_punto(&p)
    }
}

Sin #[repr(C)], los structs de Nyx se asignan en el heap con punteros del GC — no son compatibles con C. Con él, el struct se dispone exactamente como C espera.

Cómo el runtime usa FFI

El propio runtime de Nyx está construido sobre FFI. Cuando llamas tcp_listen, thread_spawn o print, esas son funciones C en el runtime:

// runtime/net.c
int64_t nyx_tcp_listen(const char* host, int64_t port) {
    // ... crear socket, bind, listen
}

El compilador conoce estas funciones y genera llamadas a ellas. Tus declaraciones FFI usan exactamente el mismo mecanismo.

Ejemplo práctico: llamar zlib para compresión

extern "C" fn nyx_compress(data: String) -> String
extern "C" fn nyx_decompress(data: String, original_size: int) -> String

fn main() {
    let original: String = "Hola Hola Hola Hola Hola"
    let comprimido: String = nyx_compress(original)
    print("Tamaño original: " + int_to_string(original.length()))
    print("Tamaño comprimido: " + int_to_string(comprimido.length()))

    let restaurado: String = nyx_decompress(comprimido, original.length())
    print("Restaurado: " + restaurado)
}

Ejemplo práctico: obtener la hora

extern "C" fn time(ptr: int) -> int

fn main() {
    let ahora: int = time(0)
    print("Timestamp Unix: " + int_to_string(ahora))
}

Consideraciones de seguridad

FFI es inherentemente unsafe. Las funciones C pueden:

Siempre valida las entradas antes de pasarlas a C, y prueba exhaustivamente. FFI es un puente entre Nyx seguro y C inseguro — trátalo con cuidado.

Ejercicios

  1. Declara y llama atoi(s: String) -> int de la biblioteca estándar de C para convertir un string a entero. Compara con string_to_int().
  1. Llama rand() -> int y srand(seed: int) de C para generar números aleatorios.
  1. Declara getenv(name: String) -> String y úsala para leer una variable de entorno.
  1. Escribe un programa que llame strlen en varios strings y compare los resultados con .length() de Nyx.
  1. Crea un struct #[repr(C)] representando un rectángulo 2D y escribe una función C (en un archivo .c) que calcule su área. Llámala desde Nyx.

Resumen

Siguiente capítulo: Unsafe y raw pointers →

← Anterior: LLVM y rendimiento Siguiente: Unsafe y punteros raw →