Bun FFI
Bun's FFI allows calling native C/C++ libraries from JavaScript.
Quick Start
import { dlopen, suffix, FFIType } from "bun:ffi";
// Load library
const lib = dlopen(libc.${suffix}, {
printf: {
args: [FFIType.cstring],
returns: FFIType.int,
},
});
// Call function lib.symbols.printf("Hello from C!\n");
Loading Libraries
Platform-Specific Paths
import { dlopen, suffix } from "bun:ffi";
// suffix is: "dylib" (macOS), "so" (Linux), "dll" (Windows)
// System library
const libc = dlopen(libc.${suffix}, { ... });
// Custom library
const myLib = dlopen(./libmylib.${suffix}, { ... });
// Absolute path const sqlite = dlopen("/usr/lib/libsqlite3.so", { ... });
Cross-Platform Loading
function getLibPath(name: string): string {
const platform = process.platform;
const paths = {
darwin: /usr/local/lib/lib${name}.dylib,
linux: /usr/lib/lib${name}.so,
win32: C:\\Windows\\System32\\${name}.dll,
};
return paths[platform] || paths.linux;
}
const lib = dlopen(getLibPath("mylib"), { ... });
FFI Types
import { FFIType } from "bun:ffi";
const types = { // Integers i8: FFIType.i8, // int8_t i16: FFIType.i16, // int16_t i32: FFIType.i32, // int32_t / int i64: FFIType.i64, // int64_t / long long
// Unsigned integers u8: FFIType.u8, // uint8_t u16: FFIType.u16, // uint16_t u32: FFIType.u32, // uint32_t u64: FFIType.u64, // uint64_t
// Floats f32: FFIType.f32, // float f64: FFIType.f64, // double
// Pointers ptr: FFIType.ptr, // void* cstring: FFIType.cstring, // const char*
// Other bool: FFIType.bool, // bool void: FFIType.void, // void };
Function Definitions
import { dlopen, FFIType, ptr, CString } from "bun:ffi";
const lib = dlopen("./libmath.so", { // Simple function add: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32, },
// String function greet: { args: [FFIType.cstring], returns: FFIType.cstring, },
// Pointer function allocate: { args: [FFIType.u64], returns: FFIType.ptr, },
// Void function log_message: { args: [FFIType.cstring], returns: FFIType.void, },
// No args get_version: { args: [], returns: FFIType.cstring, }, });
// Call functions const sum = lib.symbols.add(1, 2); // 3 const message = lib.symbols.greet(ptr(Buffer.from("World\0")));
Working with Strings
import { dlopen, FFIType, ptr, CString } from "bun:ffi";
// Passing strings to C const str = Buffer.from("Hello\0"); // Must be null-terminated lib.symbols.print_string(ptr(str));
// Receiving strings from C const result = lib.symbols.get_string(); const jsString = new CString(result); // Convert to JS string console.log(jsString.toString());
Working with Pointers
import { dlopen, FFIType, ptr, toArrayBuffer } from "bun:ffi";
const lib = dlopen("./libdata.so", { create_buffer: { args: [FFIType.u64], returns: FFIType.ptr, }, fill_buffer: { args: [FFIType.ptr, FFIType.u8, FFIType.u64], returns: FFIType.void, }, free_buffer: { args: [FFIType.ptr], returns: FFIType.void, }, });
// Allocate buffer const size = 1024; const bufPtr = lib.symbols.create_buffer(size);
// Fill buffer lib.symbols.fill_buffer(bufPtr, 0xff, size);
// Read buffer as ArrayBuffer const arrayBuffer = toArrayBuffer(bufPtr, 0, size); const view = new Uint8Array(arrayBuffer); console.log(view); // [255, 255, 255, ...]
// Free buffer lib.symbols.free_buffer(bufPtr);
Structs
import { dlopen, FFIType, ptr } from "bun:ffi";
// C struct: // struct Point { int32_t x; int32_t y; };
const lib = dlopen("./libgeom.so", { create_point: { args: [FFIType.i32, FFIType.i32], returns: FFIType.ptr, // Returns Point* }, get_distance: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.f64, }, });
// Create struct manually const point = new ArrayBuffer(8); // 2 x int32 const view = new DataView(point); view.setInt32(0, 10, true); // x = 10 view.setInt32(4, 20, true); // y = 20
// Pass to C lib.symbols.get_distance(ptr(point), ptr(point));
Callbacks
import { dlopen, FFIType, callback } from "bun:ffi";
const lib = dlopen("./libsort.so", { sort_array: { args: [FFIType.ptr, FFIType.u64, FFIType.ptr], // callback returns: FFIType.void, }, });
// Create callback const compareCallback = callback( { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32, }, (a, b) => { const aVal = new DataView(toArrayBuffer(a, 0, 4)).getInt32(0, true); const bVal = new DataView(toArrayBuffer(b, 0, 4)).getInt32(0, true); return aVal - bVal; } );
// Use callback lib.symbols.sort_array(arrayPtr, length, compareCallback.ptr);
// Close callback when done compareCallback.close();
Example: SQLite
import { dlopen, FFIType, ptr, CString } from "bun:ffi";
const sqlite = dlopen("libsqlite3.dylib", { sqlite3_open: { args: [FFIType.cstring, FFIType.ptr], returns: FFIType.i32, }, sqlite3_exec: { args: [FFIType.ptr, FFIType.cstring, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.i32, }, sqlite3_close: { args: [FFIType.ptr], returns: FFIType.i32, }, });
// Open database const dbPtrArray = new BigInt64Array(1); const dbPath = Buffer.from("test.db\0"); sqlite.symbols.sqlite3_open(ptr(dbPath), ptr(dbPtrArray)); const db = dbPtrArray[0];
// Execute query const sql = Buffer.from("CREATE TABLE test (id INTEGER);\0"); sqlite.symbols.sqlite3_exec(db, ptr(sql), null, null, null);
// Close sqlite.symbols.sqlite3_close(db);
Memory Management
// Manual allocation const buffer = new ArrayBuffer(1024); const pointer = ptr(buffer);
// Buffer stays valid as long as ArrayBuffer exists // JavaScript GC will clean up ArrayBuffer
// For C-allocated memory, call C's free function lib.symbols.free(cPointer);
Thread Safety
// FFI calls are synchronous and block the main thread // For long-running operations, use Web Workers:
// worker.ts import { dlopen } from "bun:ffi"; const lib = dlopen(...);
self.onmessage = (e) => { const result = lib.symbols.expensive_operation(e.data); self.postMessage(result); };
Common Errors
Error Cause Fix
Library not found
Wrong path Check library path
Symbol not found
Wrong function name Check function export
Type mismatch
Wrong FFI types Match C types exactly
Segmentation fault
Memory error Check pointer validity
When to Load References
Load references/type-mappings.md when:
-
Complex type conversions
-
Struct layouts
-
Union types
Load references/performance.md when:
-
Optimizing FFI calls
-
Batching operations
-
Memory pooling