Foreign Function Interface (FFI)

Part of: Reef Language Reference Last reviewed on version: 0.10.0


Extern Declarations

Declaring C Functions

extern "C" fn strlen(s: string): size_t
extern "C" fn printf(format: string): int

No function body - implementation in C library.


Type Mapping

Reef → C:

  • int → int32_t
  • int8 → int8_t
  • int64 → int64_t
  • uint32 → uint32_t
  • string → char*
  • bool → bool
  • float → double
  • float32 → float
  • size_t → size_t
  • pointer → void*

Strings across the FFI boundary (since 0.8.2)

Reef strings carry a heap-block header with an O(1) logical length; raw C strings do not. The compiler bridges the gap automatically in one direction:

  • C returning string to Reef: the result of every user-declared extern "C" fn ...: string call is boundary-copied into a Reef heap string (reef_string_dup_c). Your C function may return a static buffer, a pointer into environ, a string literal, or malloc'd memory — Reef takes a value snapshot at the call site. Two consequences: the returned Reef string does not change if your C code later rewrites its buffer, and each call costs one copy (keep chatty FFI on int/ pointer returns if that matters).
  • C filling a Reef string argument (a getcwd-style out-buffer): the buffer must not be a string literal — literals live in .rodata and a write through them faults (or, for some syscalls, fails with EFAULT and a silent wrong answer). Allocate a writable heap buffer with reef_string_alloc(n) (or equivalent) and pass that. After the fill, the buffer's logical length is stale: call str.sync_length(buf) (or str.set_length(buf, n) when the C side reports a length) before reading it in safe context. Runtime-owned fillers that already touch the heap header (reef_getcwd_safe, reef_readdir_name, reef_tls_recv, reef_string_copy_range, reef_fgets, plus str.set_length / str.sync_length) refuse a REEF_FLAG_STATIC_LITERAL buffer with a clean panic (reef-lang#71). Raw libc writers such as strcpy are not guarded — do not pass a literal as their destination.
  • Freestanding targets (--target *-baremetal): no boundary copy is emitted; your allocator/FFI owns the header contract (see reef_baremetal.h's allocator notes).

Example

extern "C" fn strlen(s: string): size_t

proc main()
    let len: size_t = strlen("Hello")
    println("String length calculated")
end main

Linking: Reef automatically links with the C standard library.


Known libc signatures (since reef-lang#72)

For a fixed set of libc names (string/stdio/stdlib/math/ctype, including ctype macros such as isxdigit), reefc keeps a name→signature table. A mismatching extern "C" is rejected at --check with both the declared and expected Reef signatures. Codegen still suppresses the emitted C extern for those names (system headers / macros / const·restrict) — do not re-emit prototypes for table names.

size_t parameters and returns must be spelled size_t (not int). char* may be declared as string or pointer. Names not in the table still rely on the C compiler's conflicting-types check when an extern is emitted.

Safety Considerations

  • Type mismatches between Reef and C can cause crashes
  • For tabled libc names, reefc checks the declaration; for other names, ensure extern declarations match actual C signatures
  • String lifetimes managed by Reef GC

Previous: 100_UNSAFE.md Next: 110_INLINE_ASSEMBLY.md Index: 000_INDEX.md