Foreign function interface
Beans has a full C ABI: it can call C functions, export C functions, describe C-layout records, pass callbacks, and read C globals. Clang owns the platform ABI lowering, so Beans matches the target’s real C rules.
Declaring and calling a C function
Section titled “Declaring and calling a C function”extern "C" fn declares an unmangled host C symbol. Calls require unsafe.
import std.io
extern "C" fn llabs(value: i64) -> i64extern "C" fn ldexp(value: f64, exponent: i32) -> f64
fn main() { unsafe { io.println("{llabs(-42)} {ldexp(1.5, 3)}") }}The ABI supports any number of integer, bool, RawPtr, CFunctionPtr, f32,
f64, or extern "C" struct/union arguments and the same return types (or no
return), including arguments past every register bank. Aggregates may contain
nested C-layout records and fixed arrays.
as "native_name" gives an import a different C symbol name.
Exporting a C function
Section titled “Exporting a C function”A pub extern "C" fn with a body exports its name for C callers. Only C-safe
parameters and results are accepted:
pub extern "C" fn add(a: i32, b: i32) -> i32 { return a + b}Build a C-facing library with beansc build --emit static --header api.h file.b. See Building.
C-layout records
Section titled “C-layout records”Use extern "C" structs and unions so the layout matches C
exactly. They can be read and written through RawPtr and Slice, and passed
by value across the boundary. extern "C" opaque struct Handle declares an
incomplete type you only touch behind RawPtr.
Callbacks
Section titled “Callbacks”- A borrowed callback is an
fn(...)parameter on anextern "C" fn. It is lent to C for the length of that one call only, so a Beans closure can be passed directly and no lifetime question arises. C must not store it or call it from another thread. - A callback C stores or calls later needs
StoredCallback<F>:StoredCallback.create(userdata_index, closure). Passfunction()to a borrowed parameter,function_pointer()when C stores the address in aCFunctionPtr<F>field, andcontext()for the separate userdata pointer. Captures must beSend + Sync. Unregister first, thenclose()(which waits for active calls). The value is move-only. CFunctionPtr<F>is C function-pointer storage, one pointer wide but distinct fromRawPtrand from Beans function values. It is valid in C-layout records, extern globals, parameters, returns, and generated headers.CFunctionPtr.null(),is_null(), and anunsafecall(...).
Globals, TLS, and errno
Section titled “Globals, TLS, and errno”C data symbols use extern "C" let, extern "C" var, or
extern "C" thread_local var, with optional as "native_name". Reads and
writes require unsafe. Hosted programs use std.c.errno() and
std.c.set_errno(value) instead of assuming a platform’s errno spelling.
Generating bindings
Section titled “Generating bindings”beansc bindgen header.h -o bindings.b asks Clang for the selected target’s
JSON AST and emits Beans C declarations:
beansc bindgen vendor/api.h -o api_bindings.b --package main -- -Ivendor/includebeansc check api_bindings.bIt handles typedefs, records, unions, arrays, enums, globals, TLS, functions,
and function pointers, with the target’s real scalar widths. In strict mode it
refuses constructs whose ABI it cannot reproduce exactly (varargs,
bitfields, flexible arrays, anonymous records, non-default calling conventions,
_Atomic members, packed/aligned records) and types with no exact Beans
equivalent (long double, 128-bit ints, _Complex, _BitInt).
--allow-unsupported omits each unsafe declaration and its dependants instead
of failing. See bindgen.
Dynamic libraries
Section titled “Dynamic libraries”To load a shared library at run time, use
std.dylib. Calling a resolved address requires
unsafe and takes one machine word per argument.
Getting the signature right
Section titled “Getting the signature right”Beans trusts the signature you declare and matches the target’s real C ABI for
it. It cannot check that declaration against the actual C function, so a wrong
argument type, count, or return type is undefined behavior at the boundary, not
a compile error. Generating declarations with beansc bindgen (above) keeps
them exact, and in strict mode it refuses any construct whose ABI it cannot
reproduce rather than guessing.
C-layout records go through structs and unions, and the raw pointers you read and write them with are in unsafe and raw memory.