| name | ffi-bindings |
| description | Create FFI bindings between Lean 4 and C code. Use when working with foreign functions, native libraries, Metal, or system APIs. |
FFI Bindings
Create Foreign Function Interface bindings between Lean 4 and C.
Quick Start
- Define opaque types for native handles
- Declare extern functions in Lean
- Implement C functions with proper memory management
- Register external classes for garbage collection
- Update build.sh for compilation
Lean Side: Opaque Types
-- Define an opaque type backed by a native pointer
opaque WindowPointed : NonemptyType
def Window : Type := WindowPointed.type
instance : Nonempty Window := WindowPointed.property
Lean Side: Extern Functions
-- Simple function
@[extern "lean_window_create"]
opaque Window.create : UInt32 → UInt32 → String → IO Window
-- Function returning a value
@[extern "lean_window_get_size"]
opaque Window.getSize : Window → IO (UInt32 × UInt32)
-- Function with no return value
@[extern "lean_window_close"]
opaque Window.close : Window → IO Unit
-- Pure function (no IO)
@[extern "lean_add_vectors"]
opaque Vec3.add : Vec3 → Vec3 → Vec3
C Side: External Class Registration
#include <lean/lean.h>
// Global class pointer (initialized once)
static lean_external_class* g_window_class = NULL;
// Destructor called when Lean GC collects the object
static void window_finalizer(void* ptr) {
Window* window = (Window*)ptr;
window_destroy(window);
}
// Initialize the class (call once at startup)
static void ensure_class_initialized() {
if (g_window_class == NULL) {
g_window_class = lean_register_external_class(
window_finalizer, // destructor
NULL // foreach (for nested lean objects)
);
}
}
C Side: Creating Objects
LEAN_EXPORT lean_obj_res lean_window_create(
uint32_t width,
uint32_t height,
lean_obj_arg title_obj,
lean_obj_arg world
) {
ensure_class_initialized();
// Extract string from Lean
const char* title = lean_string_cstr(title_obj);
// Create native object
Window* window = window_new(width, height, title);
// Wrap in Lean external object
lean_object* result = lean_alloc_external(g_window_class, window);
return lean_io_result_mk_ok(result);
}
C Side: Extracting Native Pointers
LEAN_EXPORT lean_obj_res lean_window_close(
lean_obj_arg window_obj,
lean_obj_arg world
) {
// Extract native pointer
Window* window = (Window*)lean_get_external_data(window_obj);
window_close(window);
return lean_io_result_mk_ok(lean_box(0));
}
C Side: Returning Tuples
LEAN_EXPORT lean_obj_res lean_window_get_size(
lean_obj_arg window_obj,
lean_obj_arg world
) {
Window* window = (Window*)lean_get_external_data(window_obj);
uint32_t w = window_get_width(window);
uint32_t h = window_get_height(window);
// Create tuple (UInt32 × UInt32)
lean_object* pair = lean_alloc_ctor(0, 2, 0);
lean_ctor_set(pair, 0, lean_box_uint32(w));
lean_ctor_set(pair, 1, lean_box_uint32(h));
return lean_io_result_mk_ok(pair);
}
C Side: Working with Floats
// Box a float for return
lean_object* boxed = lean_box_float(value);
// Unbox a float from argument
double value = lean_unbox_float(float_obj);
// Return Float × Float tuple
lean_object* pair = lean_alloc_ctor(0, 2, 0);
lean_ctor_set(pair, 0, lean_box_float(x));
lean_ctor_set(pair, 1, lean_box_float(y));
C Side: Working with Arrays
// Create a Lean array
lean_object* arr = lean_mk_empty_array();
for (int i = 0; i < count; i++) {
arr = lean_array_push(arr, lean_box_uint32(values[i]));
}
// Read from Lean array
size_t len = lean_array_size(arr);
for (size_t i = 0; i < len; i++) {
lean_object* elem = lean_array_get_core(arr, i);
uint32_t val = lean_unbox_uint32(elem);
}
build.sh Template
#!/bin/bash
set -e
# Set compiler for FFI
export LEAN_CC=/usr/bin/clang
# For macOS frameworks
export LIBRARY_PATH="/opt/homebrew/lib:$LIBRARY_PATH"
# Build with Lake
lake build
# Or for Metal projects on macOS
# clang -c -o ffi.o ffi.c $(pkg-config --cflags lean4)
# clang -shared -o libffi.dylib ffi.o -framework Metal -framework Foundation
Common Patterns
Optional Values
// Return none
return lean_io_result_mk_ok(lean_mk_option_none());
// Return some value
return lean_io_result_mk_ok(lean_mk_option_some(value));
Error Handling
// Return IO error
if (error) {
lean_object* err = lean_mk_io_user_error(
lean_mk_string("Error message")
);
return lean_io_result_mk_error(err);
}
Projects Using FFI
Reference implementations:
graphics/afferent- Metal GPU renderinggraphics/terminus- Terminal I/Odata/quarry- SQLite bindingsaudio/fugue- AudioToolboxnetwork/legate- gRPC