|
| 1 | +--- |
| 2 | +title: "Foreign Function Interface (FFI)" |
| 3 | +description: "Learn how to use Deno's Foreign Function Interface (FFI) to call native libraries directly from JavaScript or TypeScript. Includes examples, best practices, and security considerations." |
| 4 | +--- |
| 5 | + |
| 6 | +Deno's Foreign Function Interface (FFI) allows JavaScript and TypeScript code |
| 7 | +to call functions in dynamic libraries written in languages like C, C++, or |
| 8 | +Rust. This enables you to integrate native code performance and capabilities |
| 9 | +directly into your Deno applications. |
| 10 | + |
| 11 | +<comp.CTA href="/api/deno/ffi" type="runtime">Deno FFI Reference Docs</comp.CTA> |
| 12 | + |
| 13 | +## Introduction to FFI |
| 14 | + |
| 15 | +FFI provides a bridge between Deno's JavaScript runtime and native code. This |
| 16 | +allows you to: |
| 17 | + |
| 18 | +- Use existing native libraries within your Deno applications |
| 19 | +- Implement performance-critical code in languages like Rust or C |
| 20 | +- Access operating system APIs and hardware features not directly available in |
| 21 | + JavaScript |
| 22 | + |
| 23 | +Deno's FFI implementation is based on the `Deno.dlopen` API, which loads dynamic |
| 24 | +libraries and creates JavaScript bindings to the functions they export. |
| 25 | + |
| 26 | +## Security considerations |
| 27 | + |
| 28 | +FFI requires explicit permission using the |
| 29 | +[`--allow-ffi`](/runtime/fundamentals/security#ffi-foreign-function-interface) |
| 30 | +flag, as native code runs outside of Deno's security sandbox: |
| 31 | + |
| 32 | +```sh |
| 33 | +deno run --allow-ffi my_ffi_script.ts |
| 34 | +``` |
| 35 | + |
| 36 | +:::info |
| 37 | + |
| 38 | +⚠️ **Important security warning**: Unlike JavaScript code running in the Deno |
| 39 | +sandbox, native libraries loaded via FFI have the same access level as the Deno |
| 40 | +process itself. This means they can: |
| 41 | + |
| 42 | +- Access the filesystem |
| 43 | +- Make network connections |
| 44 | +- Access environment variables |
| 45 | +- Execute system commands |
| 46 | + |
| 47 | +Always ensure you trust the native libraries you're loading through FFI. |
| 48 | + |
| 49 | +::: |
| 50 | + |
| 51 | +## Basic usage |
| 52 | + |
| 53 | +The basic pattern for using FFI in Deno involves: |
| 54 | + |
| 55 | +1. Defining the interface for the native functions you want to call |
| 56 | +2. Loading the dynamic library using `Deno.dlopen()` |
| 57 | +3. Calling the loaded functions |
| 58 | + |
| 59 | +Here's a simple example loading a C library: |
| 60 | + |
| 61 | +```ts |
| 62 | +const dylib = Deno.dlopen("libexample.so", { |
| 63 | + add: { parameters: ["i32", "i32"], result: "i32" }, |
| 64 | +}); |
| 65 | + |
| 66 | +console.log(dylib.symbols.add(5, 3)); // 8 |
| 67 | + |
| 68 | +dylib.close(); |
| 69 | +``` |
| 70 | + |
| 71 | +## Supported types |
| 72 | + |
| 73 | +Deno's FFI supports a variety of data types for parameters and return values: |
| 74 | + |
| 75 | +| FFI Type | Deno | C | Rust | |
| 76 | +| ---------------------- | -------------------- | ------------------------ | ------------------------- | |
| 77 | +| `i8` | `number` | `char` / `signed char` | `i8` | |
| 78 | +| `u8` | `number` | `unsigned char` | `u8` | |
| 79 | +| `i16` | `number` | `short int` | `i16` | |
| 80 | +| `u16` | `number` | `unsigned short int` | `u16` | |
| 81 | +| `i32` | `number` | `int` / `signed int` | `i32` | |
| 82 | +| `u32` | `number` | `unsigned int` | `u32` | |
| 83 | +| `i64` | `bigint` | `long long int` | `i64` | |
| 84 | +| `u64` | `bigint` | `unsigned long long int` | `u64` | |
| 85 | +| `usize` | `bigint` | `size_t` | `usize` | |
| 86 | +| `isize` | `bigint` | `size_t` | `isize` | |
| 87 | +| `f32` | `number` | `float` | `f32` | |
| 88 | +| `f64` | `number` | `double` | `f64` | |
| 89 | +| `void`[1] | `undefined` | `void` | `()` | |
| 90 | +| `pointer` | `{} \| null` | `void *` | `*mut c_void` | |
| 91 | +| `buffer`[2] | `TypedArray \| null` | `uint8_t *` | `*mut u8` | |
| 92 | +| `function`[3] | `{} \| null` | `void (*fun)()` | `Option<extern "C" fn()>` | |
| 93 | +| `{ struct: [...] }`[4] | `TypedArray` | `struct MyStruct` | `MyStruct` | |
| 94 | + |
| 95 | +## Working with structs |
| 96 | + |
| 97 | +You can define and use C structures in your FFI code: |
| 98 | + |
| 99 | +```ts |
| 100 | +// Define a struct type for a Point |
| 101 | +const pointStruct = { |
| 102 | + fields: { |
| 103 | + x: "f64", |
| 104 | + y: "f64", |
| 105 | + }, |
| 106 | +} as const; |
| 107 | + |
| 108 | +// Define the library interface |
| 109 | +const signatures = { |
| 110 | + distance: { |
| 111 | + parameters: [ |
| 112 | + { struct: pointStruct }, |
| 113 | + { struct: pointStruct }, |
| 114 | + ], |
| 115 | + result: "f64", |
| 116 | + }, |
| 117 | +} as const; |
| 118 | + |
| 119 | +// Create struct instances |
| 120 | +const point1 = new Deno.UnsafePointer( |
| 121 | + new BigUint64Array([ |
| 122 | + BigInt(Float64Array.of(1.0).buffer), |
| 123 | + BigInt(Float64Array.of(2.0).buffer), |
| 124 | + ]).buffer, |
| 125 | +); |
| 126 | + |
| 127 | +const point2 = new Deno.UnsafePointer( |
| 128 | + new BigUint64Array([ |
| 129 | + BigInt(Float64Array.of(4.0).buffer), |
| 130 | + BigInt(Float64Array.of(6.0).buffer), |
| 131 | + ]).buffer, |
| 132 | +); |
| 133 | + |
| 134 | +// Call the function with structs |
| 135 | +const dist = dylib.symbols.distance(point1, point2); |
| 136 | +``` |
| 137 | + |
| 138 | +## Working with callbacks |
| 139 | + |
| 140 | +You can pass JavaScript functions as callbacks to native code: |
| 141 | + |
| 142 | +```ts |
| 143 | +const signatures = { |
| 144 | + setCallback: { |
| 145 | + parameters: ["function"], |
| 146 | + result: "void", |
| 147 | + }, |
| 148 | + runCallback: { |
| 149 | + parameters: [], |
| 150 | + result: "void", |
| 151 | + }, |
| 152 | +} as const; |
| 153 | + |
| 154 | +// Create a callback function |
| 155 | +const callback = new Deno.UnsafeCallback( |
| 156 | + { parameters: ["i32"], result: "void" } as const, |
| 157 | + (value) => { |
| 158 | + console.log("Callback received:", value); |
| 159 | + }, |
| 160 | +); |
| 161 | + |
| 162 | +// Pass the callback to the native library |
| 163 | +dylib.symbols.setCallback(callback.pointer); |
| 164 | + |
| 165 | +// Later, this will trigger our JavaScript function |
| 166 | +dylib.symbols.runCallback(); |
| 167 | + |
| 168 | +// Always clean up when done |
| 169 | +callback.close(); |
| 170 | +``` |
| 171 | + |
| 172 | +## Best practices with FFI |
| 173 | + |
| 174 | +1. Always close resources. Close libraries with `dylib.close()` and callbacks |
| 175 | + with `callback.close()` when done. |
| 176 | + |
| 177 | +2. Prefer TypeScript. Use TypeScript for better type-checking when working with |
| 178 | + FFI. |
| 179 | + |
| 180 | +3. Wrap FFI calls in try/catch blocks to handle errors gracefully. |
| 181 | + |
| 182 | +4. Be extremely careful when using FFI, as native code can bypass Deno's |
| 183 | + security sandbox. |
| 184 | + |
| 185 | +5. Keep the FFI interface as small as possible to reduce the attack surface. |
| 186 | + |
| 187 | +## Examples |
| 188 | + |
| 189 | +### Using a Rust library |
| 190 | + |
| 191 | +Here's an example of creating and using a Rust library with Deno: |
| 192 | + |
| 193 | +First, create a Rust library: |
| 194 | + |
| 195 | +```rust |
| 196 | +// lib.rs |
| 197 | +#[no_mangle] |
| 198 | +pub extern "C" fn fibonacci(n: u32) -> u32 { |
| 199 | + if n <= 1 { |
| 200 | + return n; |
| 201 | + } |
| 202 | + fibonacci(n - 1) + fibonacci(n - 2) |
| 203 | +} |
| 204 | +``` |
| 205 | + |
| 206 | +Compile it as a dynamic library: |
| 207 | + |
| 208 | +```sh |
| 209 | +rustc --crate-type cdylib lib.rs |
| 210 | +``` |
| 211 | + |
| 212 | +Then use it from Deno: |
| 213 | + |
| 214 | +```ts |
| 215 | +const libName = { |
| 216 | + windows: "./lib.dll", |
| 217 | + linux: "./liblib.so", |
| 218 | + darwin: "./liblib.dylib", |
| 219 | +}[Deno.build.os]; |
| 220 | + |
| 221 | +const dylib = Deno.dlopen( |
| 222 | + libName, |
| 223 | + { |
| 224 | + fibonacci: { parameters: ["u32"], result: "u32" }, |
| 225 | + } as const, |
| 226 | +); |
| 227 | + |
| 228 | +// Calculate the 10th Fibonacci number |
| 229 | +const result = dylib.symbols.fibonacci(10); |
| 230 | +console.log(`Fibonacci(10) = ${result}`); // 55 |
| 231 | + |
| 232 | +dylib.close(); |
| 233 | +``` |
| 234 | + |
| 235 | +### Examples |
| 236 | + |
| 237 | +- [Netsaur](https://github.com/denosaurs/netsaur/blob/c1efc3e2df6e2aaf4a1672590a404143203885a6/packages/core/src/backends/cpu/mod.ts) |
| 238 | +- [WebView_deno](https://github.com/webview/webview_deno/blob/main/src/ffi.ts) |
| 239 | +- [Deno_sdl2](https://github.com/littledivy/deno_sdl2/blob/main/mod.ts) |
| 240 | +- [Deno FFI examples repository](https://github.com/denoffi/denoffi_examples) |
| 241 | + |
| 242 | +These community-maintained repos includes working examples of FFI integrations |
| 243 | +with various native libraries across different operating systems. |
| 244 | + |
| 245 | +## Related Approaches to Native Code Integration |
| 246 | + |
| 247 | +While Deno's FFI provides a direct way to call native functions, there are other |
| 248 | +approaches to integrate native code: |
| 249 | + |
| 250 | +### Using Node-API (N-API) with Deno |
| 251 | + |
| 252 | +Deno supports [Node-API (N-API)](https://nodejs.org/api/n-api.html) for |
| 253 | +compatibility with native Node.js addons. This enables you to use existing |
| 254 | +native modules written for Node.js. |
| 255 | + |
| 256 | +Directly loading a Node-API addon: |
| 257 | + |
| 258 | +```ts |
| 259 | +import process from "node:process"; |
| 260 | +process.dlopen(module, "./native_module.node", 0); |
| 261 | +``` |
| 262 | + |
| 263 | +Using an npm package that uses a Node-API addon: |
| 264 | + |
| 265 | +```ts |
| 266 | +import someNativeAddon from "npm:some-native-addon"; |
| 267 | +console.log(someNativeAddon.doSomething()); |
| 268 | +``` |
| 269 | + |
| 270 | +How is this different from FFI? |
| 271 | + |
| 272 | +| **Aspect** | **FFI** | **Node-API Support** | |
| 273 | +| ----------- | ---------------------- | ------------------------------------------- | |
| 274 | +| Setup | No build step required | Requires precompiled binaries or build step | |
| 275 | +| Portability | Tied to library ABI | ABI-stable across versions | |
| 276 | +| Use Case | Direct library calls | Reuse Node.js addons | |
| 277 | + |
| 278 | +Node-API support is ideal for leveraging existing Node.js native modules, |
| 279 | +whereas FFI is best for direct, lightweight calls to native libraries. |
| 280 | + |
| 281 | +## Alternatives to FFI |
| 282 | + |
| 283 | +Before using FFI, consider these alternatives: |
| 284 | + |
| 285 | +- [WebAssembly](/runtime/reference/wasm/), for portable native code that runs |
| 286 | + within Deno's sandbox. |
| 287 | +- Use `Deno.run` to execute external binaries and subprocesses with controlled |
| 288 | + permissions. |
| 289 | +- Check whether [Deno's native APIs](/api/deno) already provide the |
| 290 | + functionality you need. |
| 291 | + |
| 292 | +Deno's FFI capabilities provide powerful integration with native code, enabling |
| 293 | +performance optimizations and access to system-level functionality. However, |
| 294 | +this power comes with significant security considerations. Always be cautious |
| 295 | +when working with FFI and ensure you trust the native libraries you're using. |
0 commit comments