FREAKV3 docs freak 0.15.1 (Maverick)

extern & FFI#

V3's foreign-function interface is one line long, and that is the whole of it.

The declaration form#

Illustrative snippet; not independently verified. Use the badged executable examples for tested programs.

FREAK
extern task name(param: type, param: type) -> returntype

One line, no body, no block, no attributes. Parameter and return types use the usual type grammar, but only a small set makes sense at a C boundary: int, num, bool, void. See type mapping below before reaching for word — and never for List<T> or ByteBuffer, which have no C representation.

examples/extern.fk compilesruns
-- V3's `extern` is a single flat declaration -- no `extern [C] { ... }`
-- block, no calling convention, no `link=`. Parameter and return types
-- are single identifiers.
--
-- The declared symbol must exist at link time and must not start with
-- the reserved `__freak_` prefix.
extern task abs(v: int) -> int

task main() -> void {
    say word_from_int(abs(0 - 41))
    say word_from_int(abs(41))
}
Program output
41
41

The declared symbol must be resolvable by the linker when Clang links the program. Because V3 already links the C runtime and the platform C library, ordinary libc symbols are available with no extra flags — abs above is abs(3) from the C library.

The reserved symbol namespace#

V3 mangles source task symbols into a reserved __freak_ namespace so generated code can never be shadowed by user names. Two rules follow:

  • An extern task whose native name begins with __freak_ is a hard type error. This stops a raw ABI symbol from being captured by generated locals, parameters, globals or task symbols.
  • A source task cannot redeclare a compiler builtin call name. Declaring task say(...) or extern task fs::read(...) is rejected before either backend emits code.

Type mapping#

There are no std::ffi aliases in V3 — no c_int, c_size, usize, wchar. You declare the FREAK type and the backend picks the representation.

FREAK typeLLVM representation
inti64
numdouble
booli64 (0 / 1)
wordi64 handle into the runtime's word table
voidvoid
Careful

**word is not char*.** On the LLVM backend a word is an i64 handle managed by the FREAK runtime, not a pointer to bytes. Declaring extern task puts(s: word) -> int will compile and then pass a handle where C expects a pointer. This is not hypothetical: std/algorithm.fk declares extern task freak_word_compare(a: word, b: word) -> int and the resulting array_sort_word segfaults. Treat word across extern as unsafe until you have checked the specific runtime function's real C signature.

The safe subset is int, num, bool and void. For string work, prefer the builtin word methods and the runtime functions FREAK already exposes.

Calling#

An extern task is called exactly like a FREAK task, and its arguments are type-checked against the declaration:

Illustrative snippet; not independently verified. Use the badged executable examples for tested programs.

FREAK
extern task abs(v: int) -> int

task main() -> void {
    say word_from_int(abs(0 - 41))
}

Backend differences#

The C backend emits a plain C call and relies on the symbol being declared by an included header or implicitly. The LLVM backend emits a declare for the symbol with the mapped signature. A signature that disagrees with the real C function is undefined behaviour on both — there is no cross-check.

What the bible specifies and V3 does not have#

Section 16 of the bible describes a full FFI contract. Essentially none of it is in V3:

FeatureStatus
extern [C] { ... } blocksV4 — V3 takes exactly one extern task per declaration
Calling conventions (cdecl, stdcall, system, …)V4
link="user32" library bindingV4
@link_name("symbol")V4
Variadics args: ...V4
std::ffi type aliasesV4
Raw pointers *T, *const T, *mut TV4
*ptr, .read(), .write(), .offset(), .cast<U>(), .is_null()V4
@layout(C), @layout(C, packed=N), @layout(transparent)V4
@repr(u32) on fieldless variantsV4
@extern_callback("C") and panic-abort trampolinesV4
@allow_unwinderV4
trust me "..." on my honor as .level { }V4 — does not parse
alloc, free, size_of<T>(), align_of<T>()V4
std::os::windows / macos / linuxV4
Conditional imports use[target_os="windows"] ...V4

There is no trust me block in V3, which means there is no unsafe boundary to gate — and equally, no honour-level audit trail on the code you actually compile. freak audit-trust scans source text for trust me blocks that the compiler itself would reject.

Practical guidance#

Keep the FFI surface to scalars:

Illustrative snippet; not independently verified. Use the badged executable examples for tested programs.

FREAK
extern task abs(v: int) -> int
extern task labs(v: int) -> int

task main() -> void {
    say word_from_int(abs(0 - 7))
}

If you need to hand a string to C, do it from the C side instead: add your function to freakc/runtime/freak_runtime.c, expose it the way the existing runtime bridges do, and call it through the builtin table rather than through extern. That is how fs::read, tcp::send and the ui::* family are wired, and it is the only path that handles the word representation correctly.

Badged executable examples were compiled and run with the released freak 0.15.1 (Maverick). 84/84 examples match their reviewed expected output. Illustrative snippets are labelled separately. Regenerate with python tools/refresh.py.

V4 boxes were checked against the development snapshot 7c9a1f3c27 (2026-10-04); 46/46 V4 examples match their reviewed expectation. Regenerate with python tools/verify_v4.py --checkout <Freak-lang>.