Capítulo 33 6 min de leitura

FFI e escape hatches

O Glide compila para C e linka contra a libc, o que significa que o mundo inteiro do C está a uma declaração de distância. A maioria dos programas nunca vai precisar deste capítulo. Mas quando você precisa chamar uma API de sistema que a stdlib ainda não encapsula, puxar uma biblioteca C, ou escrever à mão uma sequência de instruções crítica para desempenho, o Glide oferece um conjunto graduado de escape hatches — de "emprestar uma função da libc" até assembly puro.

Chamando uma função C: extern fn

extern fn declara uma função fornecida pelo runtime C ou pela libc. Sem corpo — apenas a assinatura, para que o verificador de tipos saiba como chamá-la e o linker resolva o símbolo:

extern fn printf(fmt: string, ...) -> i32;     // variádico estilo C com `...`
extern fn malloc(n: usize) -> *void;
extern fn free(p: *void);

fn main() -> i32 {
    printf("hex: %08x\n", 255);      // hex: 000000ff
    return 0;
}

(malloc, free, calloc, printf já são fornecidos pelo prelude — você não precisa redeclará-los. Eles aparecem aqui por serem o exemplo canônico de uma assinatura extern.)

Embutindo C: c_raw!

Quando você precisa de mais do que uma única função — um header, um helper estático, cola de plataforma — c_raw! despeja C verbatim diretamente na saída gerada. No nível superior, é onde você faz #include e define helpers:

c_raw! {
    #include <math.h>
    static double glide_hypot(double a, double b) {
        return sqrt(a * a + b * b);
    }
}

extern fn glide_hypot(a: f64, b: f64) -> f64;   // expõe o helper para o Glide

fn main() -> i32 {
    println!(glide_hypot(3.0, 4.0));     // 5
    return 0;
}

O padrão tem duas metades: c_raw! define o C, e um extern fn dá a ele uma assinatura visível para o Glide. Qualquer header que você #include antes do primeiro uso fica disponível para o código emitido posteriormente.

c_raw! também funciona dentro do corpo de uma função para algo rápido e pontual — valores do escopo ao redor ficam acessíveis no C:

fn flush_stdout() {
    c_raw! {
        fflush(stdout);
    }
}

Código por plataforma: @cfg

Código de sistemas frequentemente precisa ramificar com base no sistema operacional. @cfg("windows") / @cfg("posix") condiciona uma declaração para que apenas o alvo correspondente a compile. Defina a mesma função duas vezes, uma por plataforma:

@cfg("windows") fn os_name() -> string { return "windows"; }
@cfg("posix")   fn os_name() -> string { return "posix"; }

fn main() -> i32 {
    println!(os_name());
    return 0;
}

@cfg também condiciona um único statement ou um bloco { ... } dentro de uma função, de modo que uma função pode manter um único corpo e ramificar apenas as partes específicas do alvo:

fn describe_os() -> string {
    let mut name: string = "unknown";
    @cfg("windows") { name = "windows"; }
    @cfg("posix")   { name = "posix"; }
    return name;
}

Cada trecho condicional emite seu próprio #ifdef no C gerado, portanto o branch que não corresponde nunca chega ao compilador C.

Assembly inline: asm!

Para o raro caminho crítico onde você quer instruções exatas, asm! mapeia para a sintaxe de assembly inline do GCC: strings de instrução, depois listas opcionais de saída, entrada e clobbers separadas por :.

fn read_tsc() -> u64 {
    let lo: u32 = 0;
    let hi: u32 = 0;
    asm! volatile { "rdtsc" : "=a"(lo), "=d"(hi) }
    return ((hi as u64) << 32) | (lo as u64);
}

O marcador volatile diz ao compilador para não reordenar nem eliminar o bloco mesmo que suas saídas não pareçam ser usadas de outra forma. A forma mais simples é apenas uma instrução: asm! { "nop" }.

O escape hatch mais profundo: naked fn

Uma função naked é emitida sem prólogo nem epílogo — sem stack frame automático, sem salvar/restaurar registradores. Seu corpo deve conter apenas blocos asm!, porque você está montando a convenção de chamada inteira à mão:

@cfg("posix")
naked fn add_raw(a: i32, b: i32) -> i32 {
    asm! { "lea (%rdi,%rsi,1), %eax" }
    asm! { "ret" }
}

Este é o nível mais baixo que a linguagem alcança. Você quase nunca vai precisar disso — existe para os poucos casos (trampolins customizados, stubs de syscall, instrumentação) onde até o frame gerado por um bloco asm! normal representa uma instrução a mais.

A regra para todos esses recursos

Recap

O que vem a seguir

Esse é o livro — de glide run hello.glide até chamar assembly. Você viu a linguagem inteira e sua biblioteca: tipos, fluxo de controle, erros, memória, traits, genéricos, concorrência, empacotamento, a stdlib, serviços HTTP, testes e FFI.

A partir daqui:

  • Os [capítulos da biblioteca padrão](/book/10-prelude) — cada função pública, módulo por módulo, com exemplos.
  • Os [exemplos](/examples) — programas maiores que combinam todas as peças.

Vá construir algo de verdade com isso.