extern
extern declares that a function or static has external linkage — its implementation is provided by native code (C/C++/Rust) through the FFI layer, not by AdeshLang source. The compiler leaves the symbol unresolved for the linker and generates the appropriate calling-convention glue. AdeshLang's FFI also uses the special $cImport directive to bring C headers into scope.
Ground truth: Lexer
TokenKind::Extern(src/parsing/lexer.rs:1017, keyword"extern"), FFI docstools/ffi.md, hover Declares a foreign function for FFI (als/src/hover.rs:396),$cImportheader import,unsaferequired for raw calls.
Syntax
extern_decl ::= "extern" abi? ("fn" fn_sig ";" | "static" ident ":" type ";")
| "extern" block
abi ::= "\"C\"" | "\"C++\"" | string_literal
fn_sig ::= ident "(" params? ")" ("->" type)?
c_import ::= "$cImport" string_literal ";" // AdeshLang special: imports a C header
params ::= param ("," param)* ("," "...")? // "..." for C varargs where allowed
param ::= ident ":" type
extern_block ::= "extern" abi? "{" extern_decl* "}"
extern is reserved and cannot be used as an identifier. Declarations are statement-level and typically appear at top level or inside unsafe modules.
Canonical forms
$cImport "stdio.h";
extern fn printf(fmt: *u8, ...) -> i32;
extern "C" fn puts(s: *const u8) -> i32;
extern "C" fn add(a: i32, b: i32) -> i32;
extern "C" {
fn open(path: *const u8, flags: i32) -> i32;
fn read(fd: i32, buf: *mut u8, n: usize) -> isize;
fn close(fd: i32) -> i32;
}
// With static external symbol
extern "C" static errno: i32;
// Using an extern inside AdeshLang code — calls are unsafe-adjacent
fn main() {
unsafe {
printf("Hello from C\n");
let n = add(2, 3);
print(n); // 5
}
}
ABI string
"C" is the default and most portable (System V / Win64 ABI depending on target). "C++" enables C++ name mangling where the backend supports it. If omitted, "C" is assumed.
$cImport
$cImport "header.h"; is a special directive (not a plain import) that makes the header's declarations visible to the FFI header parser. It must appear before the corresponding extern declarations that use those types. Multiple $cImports are allowed.
Semantics
Linking model
extern items have no AdeshLang body. The compiler emits an external symbol reference; the linker resolves it against a native library (.a/.so/.dll/.lib) supplied via build flags (--link, build.adesh, or toolchain config). If the symbol cannot be resolved, linking fails.
Calling convention and ABI
The extern "C" ABI guarantees the callee uses the C calling convention (argument passing in registers/stack per target, caller-cleanup). Mismatched ABIs (e.g., declaring "C" but linking a Rust extern "Rust" symbol) is undefined behavior.
Type mapping
AdeshLang FFI maps primitive types to C types:
| AdeshLang | C |
|---|---|
i8 / u8 | int8_t / uint8_t |
i32 / u32 | int / unsigned int |
i64 / u64 | int64_t / uint64_t |
f32 / f64 | float / double |
*mut T / *const T | T* |
*u8 / string (raw) | char* |
usize / isize | size_t / ssize_t |
Complex types (struct, class) require #[repr(C)]-like layout guarantees — use raw or struct with verified layout, or opaque *mut void.
Safety
Calling an extern function is unsafe in effect, even if the call site is not syntactically unsafe in all builds. Raw pointers, varargs, and C UB (null deref, buffer overflow) are not checked by AdeshLang's borrow checker. Wrap calls in unsafe { } and validate preconditions:
unsafe {
let p: *mut u8 = alloc<u8>(64);
// ensure NUL termination for C string
p[0] = 104; p[1] = 0;
puts(p as *const u8);
free(p);
}
Compilation pipeline
- Lex
extern→TokenKind::Extern,$cImport→ special directive. - Parse as
ExternDeclwith optional ABI andfn/static/block payload. - HIR —
HirExtern { abi, name, params, ret, is_varargs }with no body. - Type check — verify AdeshLang signature matches imported header (where
$cImportprovides it). - Codegen — emit
externalsymbol withextern "C"linkage; call viacall_indirectwith C ABI. - Link — resolve against native library; emit error if unresolved.
Examples
Example 1 — Minimal C FFI with $cImport and varargs
// Import the C header so the compiler knows the signature of printf
$cImport "stdio.h";
extern fn printf(fmt: *u8, ...) -> i32;
extern "C" fn puts(s: *const u8) -> i32;
fn main() {
unsafe {
// Varargs extern — format string plus arguments
printf("Hello from C: %d\n", 42);
puts("Hello via puts" as *const u8);
}
}
// Linking (toolchain dependent):
// adesh build --link c -lc # link against libc
// or via build.adesh: extern_libs = ["c"]
Example 2 — Extern block, opaque handles, and callback
$cImport "mylib.h";
extern "C" {
// Opaque handle — AdeshLang never inspects layout
fn mylib_open(path: *const u8) -> *mut void;
fn mylib_process(handle: *mut void, data: *const u8, len: usize) -> i32;
fn mylib_close(handle: *mut void) -> void;
// Callback type: C calls back into AdeshLang
fn mylib_set_callback(handle: *mut void, cb: *mut void) -> void;
}
// AdeshLang callback that C will call — must be `extern "C"` on Adesh side too
extern "C" fn my_callback(code: i32) -> void {
print("callback:", code);
}
fn use_mylib(path: string): i32 {
unsafe {
let handle = mylib_open(path as *const u8);
if handle as usize == 0 { throw "open failed"; }
defer mylib_close(handle); // ensure close on every exit path
mylib_set_callback(handle, my_callback as *mut void);
let data = "payload";
let rc = mylib_process(handle, data as *const u8, data.len() as usize);
return rc;
}
}
Example 3 — Struct layout, raw, and mixing with region/alloc
$cImport "geometry.h";
// C struct: struct Point { double x, y; };
struct Point { x: f64, y: f64 }
extern "C" fn distance(a: *const Point, b: *const Point) -> f64;
extern "C" fn transform(points: *mut Point, n: usize) -> void;
fn demo_struct_ffi() {
unsafe {
// Heap allocation for C to mutate
let pts: *mut Point = alloc<Point>(2);
defer free(pts);
pts[0] = Point { x: 0.0, y: 0.0 };
pts[1] = Point { x: 3.0, y: 4.0 };
print(distance(&pts[0], &pts[1])); // 5.0
transform(pts, 2);
print(pts[0].x, pts[0].y);
}
// Region-based scratch for hot-path FFI — bulk-free
region scratch {
unsafe {
let tmp: *mut Point = alloc<Point>(1024);
// tmp is arena-allocated inside region — no per-alloc free needed
for i in 0..1024 { tmp[i] = Point { x: i as f64, y: 0.0 }; }
transform(tmp, 1024);
// bulk-free at region exit
}
}
}
// Raw contiguous storage for C interop (no DynArray metadata)
fn raw_array_ffi() {
let buf: raw u8[256] = raw u8[256];
unsafe {
// Pass raw pointer to C that expects `uint8_t*`
extern "C" fn fill_random(buf: *mut u8, n: usize) -> void;
fill_random(&buf[0], 256);
}
}
Restrictions / Errors
| Kind | Trigger | Diagnostic | Help |
|---|---|---|---|
| Lexical | let extern = 1 | unexpected token 'extern', expected identifier | TokenKind::Extern reserved |
| Parse | extern 42; | expected 'fn' or 'static' or '{' after 'extern' | Write extern fn f(...); |
| Parse | extern fn f() { } with body | extern function cannot have a body | Use ; not { } |
| Parse | $cImport without string literal | expected string literal after '$cImport' | Write $cImport "header.h"; |
| Type | Mismatched param/return types vs. header | mismatched FFI signature for '<name>' | Fix AdeshLang decl to match C header |
| Safety | Calling extern fn without unsafe (strict builds) | call to extern function requires unsafe | Wrap in unsafe { } |
| Link | Unresolved extern symbol at link time | undefined reference to '<name>' | Add native library to link args / build.adesh |
| ABI | extern "C++" without C++ toolchain | C++ ABI not supported on this target | Use extern "C" or enable C++ toolchain |
| Varargs | ... not last param | varargs '...' must be last parameter | Move ... to end |
| Runtime | Null pointer passed to extern expecting non-null | UB / segfault | Validate pointers before call; NUL-terminate C strings |
Common pitfall — mismatched layout:
// C: struct S { int32_t a; double b; } // padded to 16 bytes on most ABIs
struct S { a: i32, b: f64 } // may mismatch without repr guarantee
// Fix: verify layout with padding fields or use opaque *mut void and accessor fns
See Also
- unsafe —
unsafe { }audit fence for raw pointers and FFI calls - alloc / free — manual memory for FFI buffers (
*mut T) - region — arena bulk-free for scratch FFI buffers (
AllocKind::Region) - raw —
raw T[N]contiguous storage for C interop - FFI Guide — full FFI tutorial,
$cImport, linking, and build config - Modules & Imports — module system and imports
src/parsing/lexer.rs:1017,als/src/hover.rs:396,tools/ffi.md