Calling C

A module marked #![unsafe] at its top can declare C functions with extern func and call them as Cuarzo functions:

#![unsafe]

extern func strlen(cstr text) c_size
#[symbol("abs")]
extern func absolute(c_int value) c_int
extern func snprintf(ptr[c_char] buffer, c_size size, cstr format, ...) c_int

print(strlen("héllo"), absolute(-42))     // 6 42

C can break what Cuarzo promises about memory, so only modules marked unsafe may declare C functions, call them, or use the built-ins for pointers and C memory below. Other modules use what an unsafe module makes public in safe functions of its own, as the standard library does.

Module attributes come first in a file: #![unsafe], and #![link("m")] for a library the module's C functions are in. Before an extern func, #[link("user32")] names its library, or a framework of macOS as #[link("framework:Foundation")], #[symbol("name")] the C name it calls when that differs from its Cuarzo name, #[header("math.h")] a header that declares it, which is then included and the function called by its own name, and #[stdcall] the calling convention of 32-bit Windows. Libraries that differ by platform go in when branches. Without a header, the compiler declares the function itself under a name of its own that means the C symbol, so it never clashes with a declaration in a header.

The types of a C signature:

Type In C In Cuarzo
c_char, c_schar, c_uchar, c_short, c_ushort, c_int, c_uint, c_longlong, c_ulonglong the C types of those names i8, i8, u8, i16, u16, i32, u32, i64, u64
c_long, c_ulong long, unsigned long i32 and u32 on Windows, i64 and u64 elsewhere
c_size, c_ssize size_t, ptrdiff_t u64, i64
c_float, c_double float, double f32, f64
bool, c_bool _Bool bool
the Cuarzo numbers the numbers of their size themselves
cstr NUL-terminated UTF-8, const char * a str, given as it is, without a copy
wstr NUL-terminated UTF-16 a str, converted for the call
ptr[T] a pointer to a T, or NULL a pointer value; ptr[void] stands for any pointer
Handle an opaque pointer a pointer value that Cuarzo never looks into
c_func(c_int, cstr) c_int a pointer to a C function a value that can be called

The C numbers are names for Cuarzo numbers, so they can be used anywhere a number can. A cstr or wstr result becomes a str, copied, with U+FFFD for what is not valid text, and null for NULL. A ptr[T] parameter also takes bytes, which C may only read, and an array of T, which C may read and write, as where their data is; null fits any pointer, handle, or C function. out before a parameter's type makes C write it through a pointer: the call gives a variable, as in strtol(text, out rest, 10), which holds what C wrote afterwards. ... at the end takes variable arguments, which C gets as it would: whole numbers as their C types, floats as double, text as const char *.

These built-ins work with pointers and C memory:

Built-in Result
c_alloc(size), c_realloc(p, size), c_free(p) C memory, zeroed by c_alloc, that the program frees itself
deref(p), store(p, value) the T a ptr[T] points to, and writing one there
offset(p, n) the pointer n items further on: bytes for a ptr[void]
address_of(x) where the data of bytes or an array is, as a ptr
ptr_cast(p) a pointer, handle, or C function as the pointer type wanted where it is used
from_cstr(p), from_wstr(p) the text at a C string, copied
to_cstr(text), to_wstr(text) a copy of text in C memory, for c_free
ptr_to_int(p), int_to_ptr(n) a pointer as a number, and back
load_library(name) a Result[Handle]: a library loaded while the program runs
library_symbol(library, name) a Result[ptr[void]]: where a function or value of the library is
close_library(library) unloads it

A function found with library_symbol is called through a c_func of its signature, so a program can use a library only when it is there:

Handle library = load_library("libz.so.1")?
c_func(cstr) cstr version = ptr_cast(library_symbol(library, "zlibVersion")?)
print(version())

A pointer into the data of a Cuarzo value is good only while the value lives and does not grow; reading through deref a pointer that is null stops the program, but nothing checks any other pointer.

C structs, unions, and arrays

extern struct and extern union declare types with the layout of C:

extern struct Rect do
    c_long left
    c_long top
    c_long right
    c_long bottom
end

#[link("user32")]
extern func GetClientRect(Handle window, ptr[Rect] area) c_int

Rect area = Rect()                  // zeroed
GetClientRect(window, area)         // C writes it
print(area.right - area.left)

Fields have the types of C signatures, other C structs, held in place, and fixed-size arrays, c_array[c_int, 4], which are read and written an item at a time with their index checked, and whose len is their size. #[packed] before the declaration leaves out the padding between fields, and #[align(16)] aligns the type. With #[header("x.h")] the type is the one the header defines, struct Rect or the name #[symbol("RECT")] gives, and its fields are those of the header; functions that a header declares take and give such structs by value. A value of a C struct is a reference to its memory: Rect() makes one zeroed, with the fields given in order or by name set, as in Rect(right: 10); reading a field that is a struct or an array gives a reference to it, in place, so shape.corner.x = 7 changes shape; assigning a struct to a field copies it. A C struct is given to a parameter of its type by value, and to a ptr of it as where it is; a struct C gives by value is copied into a new one. deref of a pointer to a struct is the struct where the pointer points, in C memory, and store copies one there; address_of gives where a struct is.

size_of(T) and align_of(T) give the size and the alignment of a C type, as size_of(Rect) or size_of(c_array[c_short, 3]).

Callbacks and what C keeps

c_callback(f) makes a C function that calls the Cuarzo function f, for C that calls back, as a comparison for qsort, a window procedure, or an event handler. Its signature is the c_func type wanted where it is used:

extern func qsort(ptr[void] items, c_size count, c_size size, c_func(ptr[void], ptr[void]) c_int compare)

c_func(ptr[void], ptr[void]) c_int ascending = c_callback(func(ptr[void] a, ptr[void] b) c_int do
    ptr[i32] x = ptr_cast(a)
    ptr[i32] y = ptr_cast(b)
    return deref(x) - deref(y)
end)
i32[] numbers = [5, 3, 9]
qsort(numbers, len(numbers), 4, ascending)
release_callback(ascending)

A callback's frames lead to the Cuarzo code that called C, in runtime traces. Up to 32 callbacks of one signature can be alive at once; release_callback frees one that C will not call again, and a callback called after that, or on a thread other than the program's, stops the program. An extern function's name used as a value is its C function, as in c_func(cstr) c_size measure = strlen.

C may keep a pointer to a Cuarzo value only while something keeps the value alive: keep_alive(value) gives a Handle that does, which can be given to C as its user data, kept(handle) gives the value back, as the type wanted where it is used, and release_kept(handle) lets it go. A handle used after its release, or for another type, stops the program. Values do not move, so a kept value is also pinned.

on_collect(value, f) runs f once the collector finds value unreachable, to free what C gave for it, as in on_collect(image, func() do c_free(pixels) end). f must not use value itself, which would keep it reachable, and runs when the collector next runs, while the program allocates, so a close method that frees at once is better where the program knows when it is done. Without the collector (--no-gc), finalizers never run.

C enums are numbers: constants such as const c_int WM_CLOSE = 0x0010, or the values a header defines. extern const reads a constant of C, and extern var reads and writes a global variable; with #[header] they are the names the header defines, macros too:

#[header("limits.h")]
extern const c_int INT_MAX

#[header("errno.h")]
extern var c_int errno

A header in #[header] is found on GCC's include path, as <name> is, unless its path starts with ./ or ../, which are from the folder of the module, or is a full path.

Bindings from a header

cuarzo bindgen writes the Cuarzo declarations of a C header: its functions, structs and unions, enums and #define constants, and global variables.

cuarzo bindgen /usr/include/gtk-4.0/gtk/gtk.h --prefix gtk_ --link gtk-4 -o gtk.cz

GCC preprocesses the header, so its macros and includes are worked out as a C compiler would, and -I <dir> says where its includes are. The declarations written are those of the header and of the other headers in its folder and the folders in it, as a library's headers are; --prefix gtk_ keeps only those whose names start with it, in any case, so GTK_ constants too. The structs that the declarations use are written wherever they come from. --link gtk-4 adds #![link("gtk-4")], and -o writes to a file instead of to standard output.

Each C type becomes the type of a C signature: int is c_int, size_t is c_size, int32_t is i32, const char * is cstr, char *, which C may write, is ptr[c_char], a pointer to a struct the header defines is ptr[S] and to one it does not is Handle, and a pointer to a function is a c_func. An array in a struct is a c_array, and an array of arrays one c_array of all their items. A struct inside another without a name of its own in C is named after the struct and the field that hold it. A name that Cuarzo has already, as a keyword, a type, or a built-in such as abs, gets _ after it, with #[symbol] saying the C name; so does a function named as a struct, since C keeps struct tags apart (struct stat and stat()). What Cuarzo has no type for, as bit-fields, long double, and structs packed to 2 or 4 bytes, is left out, with a comment at the end that says why.

With --header <name>, each declaration has #[header(name)], so the program is compiled against the header itself: its static inline functions and macros can be called, and its structs are its own types, which extern struct with #[header] and #[symbol("type")] names. The program is then built with the header's includes found where GCC looks, such as the folders CPATH names.