Use this file to discover all available pages before exploring further.
Lean 4 ships with a well-defined Foreign Function Interface (FFI) that lets you call hand-written C or C++ code from Lean, and conversely expose Lean functions to C. The two directions use different attributes — @[extern] for importing C into Lean, and @[export] for exporting Lean to C — and both sides share the same set of C types defined in <lean/lean.h>.
The authoritative, fully detailed FFI specification lives in the Lean Language Reference. This page is a practical introduction that covers the most common patterns with runnable examples.
Mark an opaque Lean declaration with @[extern "c_function_name"] to tell the compiler that its implementation lives in an external C symbol.
-- Lean side: declare the signature, implementation is in C@[extern "my_add"]opaque myAdd : UInt32 → UInt32 → UInt32
// C side: ffi_static.c#include <stdint.h>uint32_t my_add(uint32_t a, uint32_t b) { return a + b;}
For scalar types (UInt8, UInt16, UInt32, UInt64, USize, Float, Float32), Lean passes the value directly as the corresponding C scalar — no boxing, no pointer. The Lean declaration above compiles to exactly uint32_t my_add(uint32_t, uint32_t).
To make a Lean function callable from C, annotate it with @[export lean_symbol_name]. The name you supply becomes the exact extern "C" symbol in the generated binary.
-- Expose a Lean function to external callers@[export my_length]def myLength (s : String) : UInt64 := s.length.toUInt64
The resulting C declaration is:
extern uint64_t my_length(lean_obj_arg s);
By convention, Lean’s own exported symbols use the prefix lean_. For your own libraries, choose a project-specific prefix to avoid collisions.
Everything else — String, Array, List, user-defined structures, inductive types — is represented as a lean_object * heap pointer. The header of <lean/lean.h> defines the calling-convention typedefs:
typedef lean_object * lean_obj_arg; /* Standard object argument (consumes RC) */typedef lean_object * b_lean_obj_arg; /* Borrowed object argument (caller keeps RC) */typedef lean_object * lean_obj_res; /* Standard object result (caller must dec RC) */typedef lean_object * b_lean_obj_res; /* Borrowed object result */
Lean uses reference counting for memory management. When you receive a lean_obj_arg, you own a reference and must either pass ownership on or call lean_dec. When you receive a b_lean_obj_arg, the caller owns it and you must not decrement it unless you explicitly increment first.
Non-negative integers that fit in a size_t are represented as tagged pointers: the least-significant bit is set to 1 and the value occupies the remaining bits. The runtime macros lean_box/lean_unbox convert between integers and this representation:
b_lean_obj_res lean_ctor_get(b_lean_obj_arg o, unsigned i);// Scalar field accessorsuint32_t lean_ctor_get_uint32(b_lean_obj_arg o, unsigned offset);uint64_t lean_ctor_get_uint64(b_lean_obj_arg o, unsigned offset);double lean_ctor_get_float(b_lean_obj_arg o, unsigned offset);
// Create a Lean String from a null-terminated C stringlean_obj_res lean_mk_string(const char * s);// Wrap a value in IO.Result.oklean_obj_res lean_io_result_mk_ok(lean_obj_arg val);// Check whether an IO result is okuint8_t lean_io_result_is_ok(b_lean_obj_arg r);
When calling Lean from a C program (reverse FFI), you must initialize the Lean runtime and each imported module before accessing any Lean declarations:
Lake provides build targets for compiling C/C++ files and linking them into Lean libraries. Below is the pattern from the official FFI example in the Lean 4 repository:
-- lakefile.leanimport Lakeopen System Lake DSLpackage ffi where srcDir := "lean"lean_lib FFI-- Compile the C source to an object fileinput_file ffi_static.c where path := "c" / "ffi_static.c" text := truetarget ffi_static.o pkg : FilePath := do let srcJob ← ffi_static.c.fetch let oFile := pkg.buildDir / "c" / "ffi_static.o" buildO oFile srcJob #[] #["-fPIC"] "cc"-- Link the object file into a static librarytarget libleanffi_static pkg : FilePath := do let ffiO ← ffi_static.o.fetch let name := nameToStaticLib "leanffi" buildStaticLib (pkg.staticLibDir / name) #[ffiO]-- Attach the static library to the Lean librarylean_lib FFI.Static where moreLinkObjs := #[libleanffi_static]
For a shared library (e.g., when the C code depends on Lean’s own shared library), replace buildStaticLib with buildSharedLib and use moreLinkLibs instead of moreLinkObjs.
Exposing a Lean library to an external C build system
If you want to use a Lean library from an external Makefile or CMake project, build the library with lake build and link against the generated lib*.a or lib*.so files. You also need to compile with -I $(lean --print-prefix)/include so that <lean/lean.h> is on the include path.See the tests/lake/examples/reverse-ffi/ directory in the Lean 4 repository for a minimal working example including a Makefile.