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 (
Int8throughInt64and their unsigned counterparts,Float32,Float64,Bool) —IntandFloatare aliases ofInt64andFloat64, so they count too - Pointers (
RawPointer, andPointer[T]whenTis itself FFI-safe) - Structs whose fields are themselves FFI-safe and that conform to the
FFISafeprotocol - 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.