Docs/Advanced

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:

Valk
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():

Valk
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:

Valk
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:

Valk
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:

Valk
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.

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:

Valk
link "m"
extern fn cbrt(value: f64) f64;

fn main() {
    println(cbrt(8.0))
}
  • link static "name" or link 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:

Terminal
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.