FFI

Kestrel can call C functions through the C ABI. There's no automatic binding to C++ classes or other higher-level constructs, but anything you can extern "C" from C, you can call from Kestrel.

Calling C

Declare an external function with @extern(.C) and a body-less signature:

@extern(.C) func malloc(size: Int) -> RawPointer @extern(.C) func free(ptr: RawPointer) @extern(.C) func write(fd: Int32, buf: RawPointer, count: Int) -> Int

Kestrel emits no body — the linker resolves the symbol against libc, or whatever object file you're linking. Call them like ordinary functions:

<!-- sample: continue --> let buf = malloc(1024); // ... use buf ... free(buf);

FFI-Safe Types

Not every Kestrel type can cross the C boundary. Things that can:

  • Primitive numeric types (Int8 through Int64 and their unsigned counterparts, Float32, Float64, Bool) — Int and Float are aliases of Int64 and Float64, so they count too
  • Pointers (RawPointer, and Pointer[T] when T is itself FFI-safe)
  • Structs whose fields are themselves FFI-safe and that conform to the FFISafe protocol
  • Tuples whose elements are all FFI-safe

Things that can't: generics, protocols-as-existentials, function types and closures, enums with payloads, Optional[T] (use RawPointer and check for null), and String and other heap-managed types — convert at the boundary with String.toCString() and pass a CString.

You make a struct FFI-safe by conforming it to FFISafe and ensuring its layout is plain:

struct Vec3: FFISafe { var x: Float32 var y: Float32 var z: Float32 }

The protocol carries no requirements — it's a marker the compiler uses to verify layout and prevent you from accidentally passing a non-FFI-safe type across the boundary.

When to reach for FFI

The usual cases: calling system libraries (libc, OpenGL, SQLite) and wrapping a C library you already have. For pure Kestrel-to-Kestrel code, stay on the Kestrel side — protocols and generics give you everything FFI doesn't.