Chapter 33 6 min read

FFI and escape hatches

Glide compiles to C and links against libc, which means the entire C world is one declaration away. Most programs never need this chapter. But when you have to call a system API the stdlib doesn't wrap yet, pull in a C library, or hand-write a hot instruction sequence, Glide gives you a graded set of escape hatches — from "borrow a libc function" all the way down to raw assembly.

Calling a C function: extern fn

extern fn declares a function that the C runtime or libc provides. No body — just the signature, so the typer knows how to call it and the linker resolves the symbol:

extern fn printf(fmt: string, ...) -> i32;     // C-style variadic with `...`
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 are already provided by the prelude — you don't need to re-declare them. They're shown here because they're the canonical example of an extern signature.)

Embedding C: c_raw!

When you need more than a single function — a header, a static helper, platform glue — c_raw! drops verbatim C straight into the generated output. At the top level it's where you #include and 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;   // expose the helper to Glide

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

The pattern is two halves: c_raw! defines the C, and an extern fn gives it a Glide-visible signature. Any header you #include before its first use is available to later emitted code.

c_raw! also works inside a function body for a quick one-off — values from the surrounding scope are in scope in the C:

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

Per-platform code: @cfg

Systems code often needs to branch on the OS. @cfg("windows") / @cfg("posix") gates a declaration so only the matching target compiles it. Define the same function twice, once per platform:

@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 also gates a single statement or a { ... } block inside a function, so a function can keep one body and branch only the target-specific parts:

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

Each gated piece emits its own #ifdef in the generated C, so the non-matching branch never reaches the C compiler.

Inline assembly: asm!

For the rare hot path where you want exact instructions, asm! maps to GCC's inline-assembly syntax: instruction strings, then optional output, input, and clobber lists separated by :.

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);
}

The volatile marker tells the compiler not to reorder or eliminate the block even though it looks like its outputs aren't otherwise used. The simplest form is just an instruction: asm! { "nop" }.

The deepest hatch: naked fn

A naked function is emitted with no prologue or epilogue — no automatic stack frame, no register save/restore. Its body must be only asm! blocks, because you're assembling the entire calling convention by hand:

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

This is as low as the language goes. You'll almost never need it — it exists for the handful of cases (custom trampolines, syscall stubs, instrumentation) where even a normal asm! block's generated frame is one instruction too many.

The rule for all of these

Recap

Where to next

That's the book — from glide run hello.glide to calling assembly. You've seen the whole language and its library: types, control flow, errors, memory, traits, generics, concurrency, packaging, the stdlib, HTTP services, testing, and FFI.

From here:

  • The [Standard library chapters](/book/10-prelude) — every public function, module by module, with examples.
  • The [examples](/examples) — larger programs that put the pieces together.

Go build something real with it.