Foreign Function Interface

An extern declaration names a function compiled outside Rux — in the operating system or a C library — and gives its signature, with no body. Once declared, it is called like any other function. The compiler takes the declaration on trust: it cannot look inside the library, so a declaration that does not match the real function is not an error but a program that misbehaves.

extern-decl  = attributes [ "pub" ] "extern" ( extern-func | extern-var | extern-block )
extern-func  = "func" identifier "(" [ parameters [ "," "..." ] ] ")" [ "->" type ] ";"
extern-var   = identifier ":" type ";"
extern-block = "{" { [ "pub" ] ( extern-func | extern-var ) } "}"

Every extern function and variable needs a #Link naming its library; Linking covers choosing it per platform. Its calling convention is the target's C convention unless #Abi says otherwise.

Declarations

A single function:

#Link("Kernel32.dll")
extern func GetCurrentProcessId() -> uint32;

A block groups declarations from one library, with the attributes written once. Inside a block, members are written without extern, may each be pub, and take no attributes of their own:

#Link(CRuntime)
extern {
    func strlen(text: *char8) -> uint;
    func malloc(size: uint) -> *var opaque;
    func free(memory: *var opaque);
}

An extern variable, extern Name: Type; or Name: Type; inside a block, declares data defined outside the program. It is read, written and addressed with @ like a module variable:

#Link("libc.so.6")
extern opterr: int32;   // nonzero: getopt prints its own error messages

func Quiet() {
    opterr = 0;
}

Like a function, it names its library with #Link("library") before it or on its extern { } block, and #Link("library", "symbol") before it gives it a Rux name of its own. Without one it is extern variable 'opterr' must name the library it is imported from. Its type must have a C ABI mapping, as a parameter's does — see Type mapping.

Example

The C runtime, called directly — a formatted write, a struct returned by value, and memory from malloc:

import Core::{ #target, #Error };
import Io::PrintLine;

when #target.os {
    .FreeBSD => const CRuntime = "libc.so.7";
    .Linux => const CRuntime = "libc.so.6";
    .macOS => const CRuntime = "libSystem.B.dylib";
    .Windows => const CRuntime = "msvcrt.dll";
    else => #Error("unknown C runtime")
}

// div_t: two C ints, in C's field order.
struct Div {
    quot: int32;
    rem: int32;
}

#Link(CRuntime)
extern {
    func sprintf(buffer: *var char8, format: *char8, ...) -> int32;
    func div(numerator: int32, denominator: int32) -> Div;
    func malloc(size: uint) -> *var opaque;
    func free(memory: *var opaque);
}

func Main() -> int {
    var buffer: char8[64];
    let written = sprintf(@buffer[0], "%s has %d legs".data, "spider".data, 8i32);
    PrintLine("{}", buffer[..written as uint]);   // spider has 8 legs

    let d = div(17i32, 5i32);
    PrintLine("{} r {}", d.quot, d.rem);           // 3 r 2

    let memory = malloc(16) as *var char8;
    if memory == null {
        return 1;
    }
    free(memory as *var opaque);
    return 0;
}

Type mapping

A C declaration translates type by type. Rux's int is 64 bits, so C's int is int32, never int:

CRux
int, unsigned intint32, uint32
short, long long, int64_t, …the integer of the same width
longint32 on Windows, int64 elsewhere — choose with when #target.dataModel
size_t, uintptr_tuint
double, floatfloat64, float32
char (as a byte)char8 or int8/uint8
const T **T
T * written through*var T
void **opaque or *var opaque
a pointer to a struct the code never looks inside (FILE *)a pointer to an empty struct, or *opaque
struct S by valuea Rux struct with the same fields in the same order
a function pointera function type: func(*opaque, *opaque) -> int32
void resultno ->

Floating-point widths

The C calling convention knows two floating-point types, float and double, which are float32 and float64. Rux's other widths — float8, float16, float80, float128, float256 and float512 — are passed by the compiler's own convention, which matches no C type: not _Float16, not long double, not __float128. One of them as a parameter or result of an extern function, as a parameter or result of a function marked #Abi, or as a C-variadic argument is refused:

error: extern parameter 'value' has type 'float80', which has no C ABI mapping
  note: 'float80' is passed by the compiler's own convention, which matches no C type such as '_Float16', 'long double' or '__float128'
  help: convert the value to 'float32' or 'float64' at the boundary, or pass a pointer to it

A result is reported as the return value of extern function 'sqrtl' has type 'float80', an #Abi function's parameter as parameter 'value' of '#Abi' function 'Callback' has type 'float16', and a variadic argument as a C-variadic argument has type 'float16', each with the same note and help. Convert the value to float32 or float64 at the boundary, or pass a pointer to it.

Structures are not checked.
The check reads the declared type itself, so a structure passed by value that holds a float16 or float80 field is not diagnosed. Its layout still matches no C type; do not pass one to C.

The C package declares aliases named after C's types — c_int, c_long, size_t, time_t — that resolve to the right width on each target, and binds much of the C runtime; prefer it to hand-written declarations. Struct layout follows the C rules, so a struct declared field for field matches C — see Layout.

Strings

C finds the end of a string by its zero byte. A Rux string literal keeps a NUL after its last unit — not counted by .length — so a literal's .data is a valid C string:

let greeting = "hello";
let n = strlen(greeting.data);   // 5, the same as greeting.length

That holds for literals. A slice cut from the middle of a string, or text built at run time, has no NUL after it unless the program writes one; handing its .data to C makes C read past the end. Passing a slice where C wants an address is a type error:

error: argument 1 to 'strlen' has type 'char8[..]', but parameter 'text' requires '*char8'

Pointers

Arguments that C reads or writes through are pointers: @value for one variable, @array[0] for the first element of a buffer, .data for a slice. A result C returns as void * is converted with as to the type it really points to, and checked for null — C reports failure with a sentinel, never with a panic or a fallible:

FunctionSuccessFailure
mallocan addressnull
sprintfthe bytes writtena negative count

Variadic functions

A trailing ... declares a C variadic function. Any number of arguments of any type may follow the fixed ones, and nothing checks them against what the callee expects: pass each at exactly the type the callee will read — 8i32 for %d, a float64 for %f, .data for %s. A wrong one builds and prints garbage. The one argument type the compiler does refuse is a floating-point width C lacks, such as a float16. Rux can call a C variadic function but not define one; a Rux variadic parameter (args: T...) is a different, type-checked feature — see Parameters.

Callbacks

A Rux function can be passed where C expects a function pointer, by naming it. C then calls it with C's convention, which a Rux function uses by default; mark it #Abi(.C) to keep that promise explicit. The qsort example on that page shows the whole round trip.

See also