> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/zhcndoc/bun/llms.txt
> Use this file to discover all available pages before exploring further.

# Foreign Function Interface (FFI)

> Call C, C++, Rust, Zig, and other native code from JavaScript

Bun's Foreign Function Interface (FFI) lets you call native libraries directly from JavaScript without writing bindings. It supports C ABI-compatible functions from any language.

## Basic Usage

```ts theme={null}
import { dlopen, FFIType, suffix } from "bun:ffi";

// Open a dynamic library
const lib = dlopen(
  `libmylib.${suffix}`, // .dylib, .so, or .dll
  {
    // Define function signatures
    add: {
      args: [FFIType.i32, FFIType.i32],
      returns: FFIType.i32,
    },
    get_name: {
      args: [],
      returns: FFIType.cstring,
    },
  },
);

// Call the functions
const result = lib.symbols.add(5, 3);
console.log(result); // 8

const name = lib.symbols.get_name();
console.log(name); // "MyLibrary"
```

## Opening Libraries

### Platform-Specific Extensions

```ts theme={null}
import { suffix } from "bun:ffi";

// suffix is "dylib" on macOS, "so" on Linux, "dll" on Windows
const path = `./native/lib.${suffix}`;
```

### Absolute and Relative Paths

```ts theme={null}
import { dlopen } from "bun:ffi";
import { fileURLToPath } from "url";
import { dirname, join } from "path";

const __dirname = dirname(fileURLToPath(import.meta.url));
const path = join(__dirname, "native", `lib.${suffix}`);

const lib = dlopen(path, {});
```

### System Libraries

```ts theme={null}
// On macOS and Linux, dlopen searches system paths
const lib = dlopen("libsqlite3.so", {
  sqlite3_libversion: {
    args: [],
    returns: FFIType.cstring,
  },
});
```

## FFI Types

### Primitive Types

```ts theme={null}
import { FFIType } from "bun:ffi";

// Integers
FFIType.i8    // int8_t
FFIType.i16   // int16_t
FFIType.i32   // int32_t
FFIType.i64   // int64_t (as BigInt)
FFIType.i64_fast // int64_t (as number, loses precision)

// Unsigned integers
FFIType.u8    // uint8_t
FFIType.u16   // uint16_t
FFIType.u32   // uint32_t
FFIType.u64   // uint64_t (as BigInt)
FFIType.u64_fast // uint64_t (as number)

// Floating point
FFIType.f32   // float
FFIType.f64   // double

// Other
FFIType.bool  // bool (_Bool)
FFIType.char  // char
FFIType.ptr   // void*
FFIType.void  // void (return type only)
```

### Strings

```ts theme={null}
// C-style null-terminated string
FFIType.cstring  // const char*

// Example
const lib = dlopen("lib.so", {
  greet: {
    args: [FFIType.cstring],
    returns: FFIType.void,
  },
});

lib.symbols.greet("Hello from JavaScript!");
```

### Pointers

```ts theme={null}
// Generic pointer
FFIType.ptr  // void*

// Example: function that takes a buffer
const lib = dlopen("lib.so", {
  process_data: {
    args: [FFIType.ptr, FFIType.i32], // data pointer, length
    returns: FFIType.i32,
  },
});

const buffer = new Uint8Array([1, 2, 3, 4]);
lib.symbols.process_data(buffer, buffer.length);
```

### Function Pointers

```ts theme={null}
import { CFunction } from "bun:ffi";

// Create a callback that C can call
const callback = new CFunction(
  {
    args: [FFIType.i32],
    returns: FFIType.i32,
  },
  (value) => {
    console.log("C called JavaScript with:", value);
    return value * 2;
  },
);

const lib = dlopen("lib.so", {
  register_callback: {
    args: [FFIType.ptr],
    returns: FFIType.void,
  },
});

lib.symbols.register_callback(callback.ptr);

// Important: Keep callback in scope!
// It will be garbage collected if dereferenced
```

## Working with Pointers

### Reading Memory

```ts theme={null}
import { ptr, read } from "bun:ffi";

// Read from a pointer
const value = read.i32(somePointer, offset);
const str = read.cstring(stringPointer);
const buf = read.u8(bufferPointer, length);
```

### Writing Memory

```ts theme={null}
import { ptr, write } from "bun:ffi";

// Allocate memory
const buffer = new ArrayBuffer(1024);

// Write to memory
write.i32(buffer, 42, offset);
write.f64(buffer, 3.14, offset);
```

### Pointer Casting

```ts theme={null}
import { ptr } from "bun:ffi";

// Convert TypedArray to pointer
const data = new Uint8Array(100);
const dataPtr = ptr(data);

// Pass to native function
lib.symbols.process(dataPtr, data.length);
```

## C Compiler (TinyCC)

Bun includes TinyCC for compiling C code at runtime:

```ts theme={null}
import { cc } from "bun:ffi";

const lib = cc({
  source: `
    int add(int a, int b) {
      return a + b;
    }
    
    const char* get_version() {
      return "1.0.0";
    }
  `,
  symbols: {
    add: {
      args: [FFIType.i32, FFIType.i32],
      returns: FFIType.i32,
    },
    get_version: {
      args: [],
      returns: FFIType.cstring,
    },
  },
});

console.log(lib.symbols.add(5, 3)); // 8
console.log(lib.symbols.get_version()); // "1.0.0"
```

### Including Headers

```ts theme={null}
const lib = cc({
  source: `
    #include <stdio.h>
    #include <math.h>
    
    double calculate(double x) {
      printf("Calculating...\\n");
      return sqrt(x);
    }
  `,
  symbols: {
    calculate: {
      args: [FFIType.f64],
      returns: FFIType.f64,
    },
  },
});
```

### Linking Libraries

```ts theme={null}
const lib = cc({
  source: `
    #include <sqlite3.h>
    const char* get_sqlite_version() {
      return sqlite3_libversion();
    }
  `,
  symbols: {
    get_sqlite_version: {
      args: [],
      returns: FFIType.cstring,
    },
  },
  library: ["sqlite3"], // Link against libsqlite3
});
```

### Multiple Source Files

```ts theme={null}
const lib = cc({
  source: [
    "helpers.c",
    "main.c",
  ],
  symbols: { /* ... */ },
});
```

## Performance Optimization

### Use Native Types

Prefer native sizes for best performance:

```ts theme={null}
// Slower - requires conversion
args: [FFIType.i64]

// Faster - native size
args: [FFIType.i32]
```

### Batch Operations

Minimize FFI calls by batching:

```ts theme={null}
// Slow - many FFI calls
for (let i = 0; i < 1000; i++) {
  lib.symbols.process_item(i);
}

// Fast - single FFI call
const items = new Int32Array(1000);
for (let i = 0; i < 1000; i++) items[i] = i;
lib.symbols.process_batch(items, 1000);
```

### Reuse Buffers

```ts theme={null}
// Allocate once
const buffer = new Uint8Array(1024);

// Reuse for multiple calls
for (const item of items) {
  buffer[0] = item;
  lib.symbols.process(buffer);
}
```

## Real-World Examples

### SQLite

```ts theme={null}
import { dlopen, FFIType, suffix } from "bun:ffi";

const sqlite = dlopen(`libsqlite3.${suffix}`, {
  sqlite3_open: {
    args: [FFIType.cstring, FFIType.ptr],
    returns: FFIType.i32,
  },
  sqlite3_close: {
    args: [FFIType.ptr],
    returns: FFIType.i32,
  },
  sqlite3_exec: {
    args: [
      FFIType.ptr,
      FFIType.cstring,
      FFIType.ptr,
      FFIType.ptr,
      FFIType.ptr,
    ],
    returns: FFIType.i32,
  },
});

// Open database
const db = new BigUint64Array(1);
sqlite.symbols.sqlite3_open(":memory:", ptr(db));

// Execute SQL
const sql = "CREATE TABLE users (id INTEGER, name TEXT)";
sqlite.symbols.sqlite3_exec(db[0], sql, null, null, null);

// Close
sqlite.symbols.sqlite3_close(db[0]);
```

### Image Processing (stb\_image)

```ts theme={null}
import { cc, FFIType, ptr } from "bun:ffi";

const stb = cc({
  source: `
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"
    
    unsigned char* load_image(const char* path, int* w, int* h, int* c) {
      return stbi_load(path, w, h, c, 0);
    }
    
    void free_image(unsigned char* data) {
      stbi_image_free(data);
    }
  `,
  symbols: {
    load_image: {
      args: [FFIType.cstring, FFIType.ptr, FFIType.ptr, FFIType.ptr],
      returns: FFIType.ptr,
    },
    free_image: {
      args: [FFIType.ptr],
      returns: FFIType.void,
    },
  },
  include: ["./vendor/stb"],
});

const width = new Int32Array(1);
const height = new Int32Array(1);
const channels = new Int32Array(1);

const data = stb.symbols.load_image(
  "image.png",
  ptr(width),
  ptr(height),
  ptr(channels),
);

console.log(`Image: ${width[0]}x${height[0]}, ${channels[0]} channels`);

stb.symbols.free_image(data);
```

### Rust Interop

```rust theme={null}
// lib.rs
#[no_mangle]
pub extern "C" fn fibonacci(n: i32) -> i64 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

#[no_mangle]
pub extern "C" fn process_array(data: *const i32, len: usize) -> i32 {
    let slice = unsafe { std::slice::from_raw_parts(data, len) };
    slice.iter().sum()
}
```

```ts theme={null}
// Build: cargo build --release
import { dlopen, FFIType } from "bun:ffi";

const lib = dlopen("./target/release/libmylib.dylib", {
  fibonacci: {
    args: [FFIType.i32],
    returns: FFIType.i64,
  },
  process_array: {
    args: [FFIType.ptr, FFIType.usize],
    returns: FFIType.i32,
  },
});

console.log(lib.symbols.fibonacci(10)); // 55

const arr = new Int32Array([1, 2, 3, 4, 5]);
console.log(lib.symbols.process_array(arr, 5)); // 15
```

## Memory Management

### Manual Allocation

```ts theme={null}
import { ptr } from "bun:ffi";

// JavaScript owns this memory
const buffer = new Uint8Array(1024);

// Pass to C (C should not free this)
lib.symbols.process(ptr(buffer), buffer.length);
```

### C Allocation

```ts theme={null}
// C allocates memory
const dataPtr = lib.symbols.allocate_buffer(1024);

// JavaScript reads it
const data = new Uint8Array(
  new ArrayBuffer(1024),
  dataPtr,
);

// Important: Let C free it
lib.symbols.free_buffer(dataPtr);
```

### Memory Leaks

```ts theme={null}
// Bad - memory leak
function process() {
  const callback = new CFunction({/* ... */}, () => {});
  lib.symbols.register(callback.ptr);
  // callback goes out of scope but C still has pointer!
}

// Good - keep callback alive
const callbacks = [];
function process() {
  const callback = new CFunction({/* ... */}, () => {});
  callbacks.push(callback); // Prevent GC
  lib.symbols.register(callback.ptr);
}
```

## Error Handling

### Check Return Values

```ts theme={null}
const result = lib.symbols.do_something();
if (result !== 0) {
  throw new Error(`Operation failed with code ${result}`);
}
```

### Try-Catch

```ts theme={null}
try {
  lib.symbols.risky_operation();
} catch (err) {
  console.error("FFI call failed:", err);
}
```

### errno

```ts theme={null}
import { read } from "bun:ffi";

// Some C functions set errno
const result = lib.symbols.open_file("/path");
if (result === null) {
  // Check errno in JavaScript
  console.error("Failed to open file");
}
```

## Platform Differences

### Windows

```ts theme={null}
// Windows uses different calling conventions
const lib = dlopen("library.dll", {
  WinApiFunction: {
    args: [FFIType.i32],
    returns: FFIType.i32,
    abi: "win64", // Use Windows x64 ABI
  },
});
```

### macOS Code Signing

Dynamic libraries must be signed:

```bash theme={null}
codesign -s - libmylib.dylib
```

## Best Practices

1. **Check library existence**
   ```ts theme={null}
   const path = `./lib.${suffix}`;
   if (!existsSync(path)) {
     throw new Error("Library not found");
   }
   ```

2. **Type safety**
   ```ts theme={null}
   type LibSymbols = {
     add(a: number, b: number): number;
     getName(): string;
   };

   const lib = dlopen<LibSymbols>(path, {/* ... */});
   ```

3. **Resource cleanup**
   ```ts theme={null}
   const lib = dlopen(path, symbols);
   try {
     // Use library
   } finally {
     lib.close();
   }
   ```

4. **Document ABIs**
   ```ts theme={null}
   // Document the C signature
   // C: int add(int a, int b)
   add: {
     args: [FFIType.i32, FFIType.i32],
     returns: FFIType.i32,
   }
   ```

## Debugging

```ts theme={null}
// Enable FFI debug logging
process.env.BUN_DEBUG_FFI = "1";

// Check if library loaded
const lib = dlopen(path, symbols);
console.log("Symbols:", Object.keys(lib.symbols));

// Validate pointers
const ptr = lib.symbols.get_data();
console.log("Pointer:", ptr.toString(16));
```

## Performance

FFI is extremely fast in Bun:

* Overhead: \~5-10ns per call
* Zero-copy for buffers
* JIT-compiled trampolines
* SIMD optimization for bulk transfers

Benchmark:

```ts theme={null}
const start = performance.now();
for (let i = 0; i < 1_000_000; i++) {
  lib.symbols.add(i, i);
}
console.log(`1M calls: ${performance.now() - start}ms`);
// ~15ms on M1 Mac
```
