FFI — Calling C code
Why call C?
Nyx is powerful, but the world runs on C. Operating systems, graphics libraries, databases, compression algorithms — decades of battle-tested C code exist. Instead of rewriting everything, Nyx lets you call C functions directly. This is called FFI — Foreign Function Interface.
Declaring an external function
To call a C function from Nyx, you declare it with extern "C":
extern "C" fn abs(x: int) -> int fn main() { print(abs(-42)) // 42 print(abs(10)) // 10 }
This tells the compiler: "there is a C function called abs that takes an int and returns an int. I will provide it at link time."
The C standard library is linked automatically, so functions like abs just work.
Type mapping
When you call C, types must match. Here is how Nyx types map to C:
| Nyx type | C type | 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 |
When you pass a Nyx String to a C function, the compiler automatically converts it to a null-terminated char*.
Calling math functions
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 root: float = sqrt(144.0) print(root) // 12.0 let result: float = pow(2.0, 10.0) print(result) // 1024.0 }
Link with -lm (the math library) when compiling.
Calling string functions
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 comes first") } }
C-compatible structs
To pass structs to C, they must have C-compatible memory layout. Use #[repr(C)]:
#[repr(C)] struct Point { x: i64, y: i64 } extern "C" fn process_point(p: *Point) -> int fn main() { var p: Point = Point { x: 10, y: 20 } // Pass pointer to C function unsafe { let result: int = process_point(&p) } }
Without #[repr(C)], Nyx structs are heap-allocated with GC pointers — not compatible with C. With it, the struct is laid out exactly as C expects.
How the runtime uses FFI
Nyx's own runtime is built on FFI. When you call tcp_listen, thread_spawn, or print, those are C functions in the runtime:
// runtime/net.c
int64_t nyx_tcp_listen(const char* host, int64_t port) {
// ... create socket, bind, listen
}
The compiler knows about these functions and generates calls to them. Your FFI declarations use the exact same mechanism.
Practical example: calling zlib for compression
extern "C" fn nyx_compress(data: String) -> String extern "C" fn nyx_decompress(data: String, original_size: int) -> String fn main() { let original: String = "Hello Hello Hello Hello Hello" let compressed: String = nyx_compress(original) print("Original size: " + int_to_string(original.length())) print("Compressed size: " + int_to_string(compressed.length())) let restored: String = nyx_decompress(compressed, original.length()) print("Restored: " + restored) }
Practical example: getting the time
extern "C" fn time(ptr: int) -> int fn main() { let now: int = time(0) print("Unix timestamp: " + int_to_string(now)) }
Safety considerations
FFI is inherently unsafe. C functions can:
- Crash if given bad pointers
- Write past buffer boundaries
- Return invalid data
- Leak memory
Always validate inputs before passing them to C, and test thoroughly. FFI is a bridge between safe Nyx and unsafe C — treat it with care.
Exercises
- Declare and call
atoi(s: String) -> intfrom the C standard library to convert a string to an integer. Compare withstring_to_int().
- Call
rand() -> intandsrand(seed: int)from C to generate random numbers.
- Declare
getenv(name: String) -> Stringand use it to read an environment variable.
- Write a program that calls
strlenon several strings and compares the results with Nyx's.length().
- Create a
#[repr(C)]struct representing a 2D rectangle and write a C function (in a.cfile) that computes its area. Call it from Nyx.
Summary
extern "C" fn name(params) -> ReturnTypedeclares a C function.- Nyx types map to C types:
int→i64,String→char*,float→double. - String conversion (Nyx to C
char*) happens automatically. #[repr(C)]makes struct layout C-compatible.- The Nyx runtime itself uses FFI for all system operations.
- FFI is powerful but unsafe — validate inputs and test carefully.
Next chapter: Unsafe and raw pointers →