Skip to content

Commit b07d2e4

Browse files
thisisjofranklittledivyry
authoredMar 18, 2025··
add page on ffi (#1571)
Co-authored-by: Divy Srivastava <[email protected]> Co-authored-by: Ryan Dahl <[email protected]>
1 parent df99800 commit b07d2e4

File tree

5 files changed

+308
-7
lines changed

5 files changed

+308
-7
lines changed
 

‎runtime/_data.ts

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ export const sidebar = [
7777
title: "HTTP Server",
7878
href: "/runtime/fundamentals/http_server/",
7979
},
80+
{
81+
title: "FFI",
82+
href: "/runtime/fundamentals/ffi/",
83+
},
8084
{
8185
title: "OpenTelemetry",
8286
href: "/runtime/fundamentals/open_telemetry/",

‎runtime/fundamentals/ffi.mdx

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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.

‎runtime/fundamentals/index.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Fundamentals
3-
description: "The core guide to Deno's fundamental concepts and features. Learn about built-in tooling, TypeScript support, Node.js compatibility, security model, and modern JavaScript features that make Deno powerful and developer-friendly."
3+
description: "A guide to Deno's fundamental concepts and features. Learn about built-in tooling, TypeScript support, Node.js compatibility, security model, and modern JavaScript features that make Deno powerful and developer-friendly."
44
---
55

66
Deno is designed with the developer in mind, aiming to provide a smooth and
@@ -9,7 +9,7 @@ easy to pick up, even for those new to the backend development.
99

1010
## Built in tooling
1111

12-
Denos inbuilt tooling significantly eases the onboarding process. With a single
12+
Deno's inbuilt tooling significantly eases the onboarding process. With a single
1313
executable, you can get started without worrying about complex setups or
1414
dependencies. This allows you to focus on writing code rather than configuring
1515
your environment.
@@ -34,8 +34,8 @@ can integrate Deno into your projects seamlessly.
3434

3535
Deno comes with a comprehensive standard library written in TypeScript. This
3636
library includes modules for common tasks such as HTTP servers, file system
37-
operations, and more, allowing you to avoid reinventing the wheel and focus on
38-
your applications features.
37+
operations, and more, allowing you to avoid "reinventing the wheel" and focus on
38+
your application's features.
3939

4040
- [Standard Library](/runtime/fundamentals/standard_library/)
4141

@@ -47,6 +47,7 @@ vulnerabilities. This secure-by-default approach helps protect your applications
4747
from potential threats.
4848

4949
- [Security and permissions](/runtime/fundamentals/security/)
50+
- [Foreign Function Interface (FFI)](/runtime/fundamentals/ffi/)
5051

5152
## Modern Language Features
5253

‎runtime/fundamentals/security.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,9 @@ scripts for npm packages will be executed as a subprocess.
388388

389389
### FFI (Foreign Function Interface)
390390

391-
Deno provides a mechanism for executing code written in other languages, such as
392-
Rust, C, or C++, from within a Deno runtime. This is done using the
391+
Deno provides an
392+
[FFI mechanism for executing code written in other languages](/runtime/fundamentals/ffi/),
393+
such as Rust, C, or C++, from within a Deno runtime. This is done using the
393394
`Deno.dlopen` API, which can load shared libraries and call functions from them.
394395

395396
By default, executing code can not use the `Deno.dlopen` API, as this would

‎runtime/reference/deno_namespace_apis.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Refer to the links below for code examples for common functions.
4545
## Subprocesses
4646

4747
The Deno runtime comes with
48-
[built-in functions for spinning up subprocesses](/api/deno/sub-process).
48+
[built-in functions for spinning up subprocesses](/api/deno/subprocess).
4949

5050
Refer to the links below for code samples of how to create a subprocess.
5151

0 commit comments

Comments
 (0)
Please sign in to comment.