Calling a C library
Declare libc's strlen, getpid, getenv, snprintf and qsort in an extern "C" block, and manage C memory directly with the pointer and allocation functions of rok::bare.
Code
use rok::bare::alloc::{Layout, LayoutError, alloc, dealloc};
use rok::bare::ptr::{CPtr, ConstPtr, ManyPtr, Ptr};
extern "C" {
fn strlen(s: str) -> usize;
fn getpid() -> i32;
fn setenv(name: str, value: str, overwrite: i32) -> i32;
fn getenv(name: str) -> CPtr<c_char>; // NULL when unset
fn snprintf(buf: Ptr<u8>, size: usize, fmt: str, ...) -> i32;
fn qsort(base: Ptr<u8>, n: usize, size: usize, cmp: extern "C" fn(ConstPtr<i32>, ConstPtr<i32>) -> i32);
}
// A C string (NUL-terminated chars) copied into a rok `str`.
unsafe fn from_c(p: ConstPtr<c_char>) -> str {
let bytes: List<u8> = [];
let i = 0usize;
loop {
let b = p.add(i).read();
if b == c'\0' { break; }
bytes.push(b as u8);
i += 1;
}
try str::from_utf8(bytes) catch _ { "<invalid utf-8>" }
}
// getenv with the NULL case turned into `none`.
fn c_getenv(name: str) -> str | none {
let p = unsafe { getenv(name) }.as_const()?;
unsafe { from_c(p) }
}
// The comparator qsort calls back: a rok function with the C ABI.
extern "C" fn descending(a: ConstPtr<i32>, b: ConstPtr<i32>) -> i32 {
let (x, y) = unsafe { (a.read(), b.read()) };
if x < y { 1 } else if x > y { -1 } else { 0 }
}
fn main() throws (LayoutError) {
// str arguments arrive in C as NUL-terminated char*.
println(f"strlen(\"hello, rok\") = {unsafe { strlen("hello, rok") }}");
println(f"strlen(\"naïve\") = {unsafe { strlen("naïve") }} bytes");
let pid = unsafe { getpid() };
println(f"getpid() > 0: {pid > 0}, matches process::id(): {pid as u32 == rok::std::process::id()}");
unsafe { setenv("ROK_FFI_DEMO", "set from C", 1) };
println(f"ROK_FFI_DEMO = {c_getenv("ROK_FFI_DEMO") ?? "(unset)"}");
println(f"ROK_FFI_MISSING = {c_getenv("ROK_FFI_MISSING") ?? "(unset)"}");
// A variadic call writing into a buffer we allocate.
let text_layout = try Layout::array::<u8>(64);
let buf: Ptr<u8> = unsafe { alloc(text_layout) }.as_ptr() ?? panic("out of memory");
let n = unsafe { snprintf(buf, 64, "%-6s|%5d|%08.3f", "pi", 42, 3.14159) };
println(f"snprintf wrote {n} bytes: {unsafe { from_c(buf.cast::<c_char>().as_const()) }}");
unsafe { dealloc(buf, text_layout) };
// qsort over a C array of i32.
let values: List<i32> = [42, 7, 19, 88, 3, 56, 21];
let layout = try Layout::array::<i32>(values.len());
let base: Ptr<u8> = unsafe { alloc(layout) }.as_ptr() ?? panic("out of memory");
let arr: ManyPtr<i32> = base.cast::<i32>().as_many();
for (i, v) in values.iter().enumerate() { unsafe { arr.write(i, v) }; }
unsafe { qsort(base, values.len(), 4, descending) };
let sorted: List<i32> = [];
for i in 0..values.len() { sorted.push(unsafe { arr.read(i) }); }
println(f"qsort descending: {sorted:?}");
unsafe { dealloc(base, layout) };
}
strlen("hello, rok") = 10
strlen("naïve") = 6 bytes
getpid() > 0: true, matches process::id(): true
ROK_FFI_DEMO = set from C
ROK_FFI_MISSING = (unset)
snprintf wrote 21 bytes: pi | 42|0003.142
qsort descending: [88, 56, 42, 21, 19, 7, 3]
Walkthrough
- An
extern "C" { ... }block holds only signatures without bodies. Functions declared there are implicitlyunsafe, so every call goes insideunsafe { ... }(5.9externblocks, 4.15 unsafe). - A
strparameter is passed to C as a NUL-terminatedchar*. That is whystrlen("naïve")returns 6, the UTF-8 byte count, not the 5 characters (5.9.4 FFI-compatible types). - A C pointer that may be NULL is received as a
CPtr.as_const()gives a non-nullConstPtrornone, so a single?wrapsgetenvinto a safe function returningstr | none(Sum types). - Memory is allocated by hand with
Layoutandalloc/dealloc, and accessed like a C array throughManyPtr'sread(i)/write(i, v). The GC does not manage it, so you free it yourself. extern "C" fn descendingis a rok function compiled with the C ABI. Passed toqsortas a function pointer, it lets C call back into rok code while sorting (5.9.10 definingextern fn).- Variadic functions such as
snprintfare declared with...at the end of the signature. See the FFI chapter for more.