Bun FFI (Foreign Function Interface)
Bun provides FFI (Foreign Function Interface) functionality that allows direct calling of native libraries written in C/C++/Rust. This chapter introduces Bun FFI usage.
FFI Introduction
FFI allows JavaScript to call native code directly without writing Node.js native plugins or WebAssembly.
Advantages
- High Performance: Direct native code calls, no IPC overhead
- Simple: No need to compile binding code
- Flexible: Can call any C ABI compatible library
Limitations
- Need to understand the target library's C API
- Memory management requires special attention
- Different platforms may require different library files
Basic Usage
Loading Dynamic Libraries
typescript
import { dlopen, FFIType, suffix } from "bun:ffi";
// Automatically handle platform differences
// suffix: macOS -> "dylib", Linux -> "so", Windows -> "dll"
const libPath = `./libmath.${suffix}`;
const lib = dlopen(libPath, {
// Declare function signatures
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
});
// Call function
const result = lib.symbols.add(2, 3);
console.log(result); // 5System Libraries
typescript
import { dlopen, FFIType } from "bun:ffi";
// Load C standard library
const libc = dlopen("libc.so.6", {
getpid: {
args: [],
returns: FFIType.i32,
},
getenv: {
args: [FFIType.cstring],
returns: FFIType.cstring,
},
});
console.log("PID:", libc.symbols.getpid());
console.log("HOME:", libc.symbols.getenv("HOME"));Data Types
FFIType Types
| FFIType | C Type | Description |
|---|---|---|
bool | bool | Boolean |
i8 | int8_t | 8-bit signed integer |
u8 | uint8_t | 8-bit unsigned integer |
i16 | int16_t | 16-bit signed integer |
u16 | uint16_t | 16-bit unsigned integer |
i32 | int32_t | 32-bit signed integer |
u32 | uint32_t | 32-bit unsigned integer |
i64 | int64_t | 64-bit signed integer |
u64 | uint64_t | 64-bit unsigned integer |
f32 | float | 32-bit float |
f64 | double | 64-bit double |
ptr | void* | Pointer |
cstring | char* | C string |
Type Examples
typescript
import { dlopen, FFIType, ptr, toArrayBuffer, toBuffer } from "bun:ffi";
const lib = dlopen("./mylib.so", {
// Integer types
processInt: {
args: [FFIType.i32],
returns: FFIType.i32,
},
// Float types
calculateFloat: {
args: [FFIType.f64, FFIType.f64],
returns: FFIType.f64,
},
// Boolean type
checkCondition: {
args: [FFIType.bool],
returns: FFIType.bool,
},
// String
processString: {
args: [FFIType.cstring],
returns: FFIType.cstring,
},
// Pointer
allocateBuffer: {
args: [FFIType.u32],
returns: FFIType.ptr,
},
});Pointer Operations
Creating Pointers
typescript
import { ptr, toArrayBuffer, toBuffer } from "bun:ffi";
// Get pointer from TypedArray
const buffer = new Uint8Array([1, 2, 3, 4]);
const pointer = ptr(buffer);
console.log("Pointer address:", pointer);Reading Pointer Data
typescript
import { toArrayBuffer, toBuffer } from "bun:ffi";
// Assume lib.symbols.getData returns a pointer
const dataPtr = lib.symbols.getData();
// Convert to ArrayBuffer (need to know length)
const arrayBuffer = toArrayBuffer(dataPtr, 0, 100); // 100 bytes
// Convert to Buffer
const buffer = toBuffer(dataPtr, 0, 100);Passing Buffers
typescript
import { ptr } from "bun:ffi";
const lib = dlopen("./mylib.so", {
processData: {
args: [FFIType.ptr, FFIType.u32],
returns: FFIType.i32,
},
});
// Create buffer
const data = new Uint8Array([1, 2, 3, 4, 5]);
// Pass pointer and length
const result = lib.symbols.processData(ptr(data), data.length);String Handling
C Strings
typescript
import { dlopen, FFIType, CString } from "bun:ffi";
const lib = dlopen("./strlib.so", {
getString: {
args: [],
returns: FFIType.cstring,
},
setString: {
args: [FFIType.cstring],
returns: FFIType.void,
},
});
// Get C string
const cstr = lib.symbols.getString();
console.log(cstr); // Automatically converted to JS string
// Pass string
lib.symbols.setString("Hello from JavaScript");Encoders
typescript
// Convert JS string to C string
const encoder = new TextEncoder();
const bytes = encoder.encode("Hello\0"); // Include null terminator
const lib = dlopen("./strlib.so", {
processBytes: {
args: [FFIType.ptr],
returns: FFIType.void,
},
});
lib.symbols.processBytes(ptr(bytes));Structs
Passing Structs
typescript
import { ptr } from "bun:ffi";
// C struct:
// struct Point {
// float x;
// float y;
// };
// Create struct data
const point = new Float32Array([3.14, 2.71]);
const lib = dlopen("./geometry.so", {
printPoint: {
args: [FFIType.ptr],
returns: FFIType.void,
},
});
lib.symbols.printPoint(ptr(point));Struct Arrays
typescript
// struct Point { float x; float y; };
// Each Point is 8 bytes
// Create array of 3 points
const points = new Float32Array([
1.0, 2.0, // Point 1
3.0, 4.0, // Point 2
5.0, 6.0, // Point 3
]);
const lib = dlopen("./geometry.so", {
processPoints: {
args: [FFIType.ptr, FFIType.u32],
returns: FFIType.void,
},
});
lib.symbols.processPoints(ptr(points), 3);Callback Functions
JavaScript Callbacks
typescript
import { callback, FFIType } from "bun:ffi";
// Create callback function
const cb = callback({
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
}, (a, b) => {
console.log(`Callback called: ${a} + ${b}`);
return a + b;
});
const lib = dlopen("./mylib.so", {
registerCallback: {
args: [FFIType.ptr],
returns: FFIType.void,
},
triggerCallback: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
});
// Register callback
lib.symbols.registerCallback(cb);
// Trigger callback
const result = lib.symbols.triggerCallback(10, 20);
console.log("Result:", result); // 30Practical Examples
Calling SQLite (Without Built-in)
typescript
import { dlopen, FFIType, ptr, CString } from "bun:ffi";
const sqlite = dlopen("libsqlite3.so", {
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,
},
});
// Using SQLite
// ...Calling System APIs
typescript
import { dlopen, FFIType } from "bun:ffi";
// macOS example
const foundation = dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", {
// ...
});
// Linux example: get system info
const libc = dlopen("libc.so.6", {
uname: {
args: [FFIType.ptr],
returns: FFIType.i32,
},
});Calling Rust Libraries
rust
// src/lib.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn greet(name: *const std::os::raw::c_char) -> *const std::os::raw::c_char {
let c_str = unsafe { std::ffi::CStr::from_ptr(name) };
let name = c_str.to_str().unwrap();
let greeting = format!("Hello, {}!", name);
std::ffi::CString::new(greeting).unwrap().into_raw()
}bash
# Compile Rust library
cargo build --releasetypescript
// Call Rust library
import { dlopen, FFIType, suffix } from "bun:ffi";
const lib = dlopen(`./target/release/libmylib.${suffix}`, {
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
greet: {
args: [FFIType.cstring],
returns: FFIType.cstring,
},
});
console.log(lib.symbols.add(5, 3)); // 8
console.log(lib.symbols.greet("Bun")); // "Hello, Bun!"Error Handling
Checking Return Values
typescript
const lib = dlopen("./mylib.so", {
riskyOperation: {
args: [FFIType.ptr],
returns: FFIType.i32, // 0 = success, -1 = failure
},
});
const result = lib.symbols.riskyOperation(null);
if (result !== 0) {
throw new Error(`Operation failed, error code: ${result}`);
}Closing Libraries
typescript
const lib = dlopen("./mylib.so", {
// ...
});
// Close when done
lib.close();Performance Considerations
Avoiding Frequent Calls
typescript
// ❌ Bad: frequent FFI calls
for (let i = 0; i < 1000000; i++) {
lib.symbols.add(i, 1);
}
// ✅ Good: batch processing
const data = new Int32Array(1000000);
lib.symbols.processArray(ptr(data), data.length);Caching Function References
typescript
// Cache symbol references
const { add, multiply, divide } = lib.symbols;
// Direct call
const sum = add(1, 2);Summary
This chapter covered:
- ✅ FFI basic concepts and usage
- ✅ Data type mapping
- ✅ Pointer and buffer operations
- ✅ String handling
- ✅ Struct passing
- ✅ Callback functions
- ✅ Calling Rust library examples
Next Steps
Continue reading Performance Optimization to learn about Bun's performance tuning techniques.