C interop
Call C libraries and share structs with C.
On this page
Valk calls C functions directly: declare them with extern and link the library. Structs have the C layout, so they can be passed by value or by pointer. Raw pointer work follows the rules in Unsafe code.
Declare a C function#
extern fn declares a symbol that the linker provides. The signature must match the C header exactly:
extern fn abs(value: i32) i32;
fn main() {
println(abs(-7)) // 7
}
Use fixed-width types that match C:
| C | Valk |
|---|---|
int, unsigned int |
i32, u32 |
long long, int64_t |
i64 |
size_t |
uint |
float, double |
f32, f64 |
char * (zero-terminated) |
cstring |
void * |
ptr |
T * |
*T |
| function pointer | fnptr(args)(result) |
extern shared name: T; declares a C global, such as extern shared stdout: ?ptr;.
Strings#
A Valk String has no zero terminator. Pass .data_cstring, which is @unsafe because the pointer is only valid while the string is alive. A returned cstring converts with .to_string():
extern fn strlen(text: cstring) uint;
extern fn getenv(name: cstring) ?cstring;
fn main() {
@unsafe
println(strlen("hello".data_cstring)) // 5
let home = getenv("HOME".data_cstring)
if isset(home) : println(home.to_string().bytes > 0) // true
}
Pointers and callbacks#
A pointer parameter takes an address: pass &value. A Valk function can be passed where C expects a function pointer:
link "m"
extern fn frexp(value: f64, exponent: *i32) f64;
extern fn qsort(base: ptr, count: uint, size: uint, compare: fnptr(ptr, ptr)(i32)) void;
fn compare_ints(a: ptr, b: ptr) i32 {
return @ptrv(a, i32) - @ptrv(b, i32)
}
fn main() {
let exponent: i32 = 0
let mantissa = frexp(8.0, &exponent)
println("%mantissa %exponent") // 0.5 4
let numbers = Array[i32]{ 3, 1, 2 }
@unsafe
qsort(numbers.items(), numbers.length, 4, compare_ints)
println(numbers.join(", ")) // 1, 2, 3
}
Structs#
A struct has the same layout as a C struct with the same fields in the same order, and is passed and returned by value the way the platform's C compiler does it:
struct DivResult {
quot: i32
rem: i32
}
extern fn div(numerator: i32, denominator: i32) DivResult;
fn main() {
let result = div(17, 5)
println("%{result.quot} %{result.rem}") // 3 2
}
Declare every field the C struct has: a struct that is too small lets C write past its end. For a C function that takes T *, pass &value.
Variadic functions#
... as the last parameter declares a C variadic function. Extra arguments follow C promotion rules (f32 becomes double, small integers become i32), and only numbers and raw pointers can be passed:
extern fn printf(format: cstring, ...) i32;
fn main() {
@unsafe
printf("\%d \%.1f\n".data_cstring, 42.to(i32), 1.5) // 42 1.5
}
\% writes a literal %, since % starts interpolation in Valk strings. Only extern functions can be variadic.
Link a library#
Name the library in the source with link. link "m" links libm as a .so, .a, .dylib, .dll or .lib file, depending on the target:
link "m"
extern fn cbrt(value: f64) f64;
fn main() {
println(cbrt(8.0))
}
link static "name"orlink dynamic "name"forces one kind.link ":libname.a"links that exact file name.
Or on the command line, with -l for the library and -L for a directory to search:
valk build src -o app -l sqlite3 -L ./native/lib
valk build src -o app -l sqlite3 -L ./native/lib --static
Combine link with #if OS == ... when libraries differ per platform. The library must be built for the same target as the program; see Cross compiling.
Go the other way#
To let C or another language call Valk, see Building native libraries.

