new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Deno · Fundamentals · all subjects

ffi

18 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Deno.dlopen() basic usage pattern

To use FFI in Deno, the basic pattern involves: 1) Define the interface for the native functions you want to call, 2) Load the dynamic library using Deno.dlopen(), and 3) Call the loaded functions. Example: const dylib = Deno.dlopen("libexample.so", { add: { parameters: ["i32", "i32"], result: "i32" } }); console.log(dylib.symbols.add(5, 3)); dylib.close();

Library path resolution with and without separators

Paths to dynamic libraries follow OS rules, not Deno's module resolution. A path with a separator (e.g., "./libexample.so" or "/usr/lib/libexample.so") is opened from that exact location. A relative path like "./libexample.so" is resolved against the current working directory of the process, not the location of the .ts file. A bare name with no separator (e.g., "libexample.so") is left to the OS to find on its standard search path: on Linux that means LD_LIBRARY_PATH and the system cache like /usr/lib; on macOS the DYLD_* paths; on Windows the executable's directory, system directories, and PATH.

Resolve library paths relative to module with import.meta.url

To load a library that ships next to your source file regardless of the current working directory, resolve the path against import.meta.url instead of using a bare relative string. Example: const path = new URL("./libexample.so", import.meta.url).pathname; const dylib = Deno.dlopen(path, { add: { parameters: ["i32", "i32"], result: "i32" } } as const);

Bundle native libraries with deno compile using --include flag

Deno.dlopen() needs a real file on disk, so a dynamic library is not embedded in a compiled binary automatically. Include it explicitly with the --include flag: deno compile --allow-ffi --include libexample.so main.ts At runtime Deno unpacks the included library to a temporary directory and points import.meta.url at it, so a module that resolves its path with new URL("./libexample.so", import.meta.url).pathname finds the bundled copy.

Deno.dlopen() throws synchronously on load failures

Deno.dlopen() throws synchronously when the library cannot be loaded or when a declared symbol is missing. A missing or unreadable file reports "Could not open library: ...". A missing function reports "Failed to register symbol <name>: ...". Wrap the call in a try/catch to fail gracefully.

FFI supported types mapping

Deno's FFI supports the following data types for parameters and return values: i8 (number, char/signed char, i8), u8 (number, unsigned char, u8), i16 (number, short int, i16), u16 (number, unsigned short int, u16), i32 (number, int/signed int, i32), u32 (number, unsigned int, u32), i64 (bigint, long long int, i64), u64 (bigint, unsigned long long int, u64), usize (bigint, size_t, usize), isize (bigint, size_t, isize), f32 (number, float, f32), f64 (number, double, f64), void (undefined, void, () - can only be used as result type), pointer ({} | null, void *, *mut c_void), buffer (TypedArray | null, uint8_t *, *mut u8 - accepts TypedArrays as parameter but returns pointer object or null), function ({} | null, void (*fun)(), Option<extern "C" fn()>), struct { struct: [...] } (TypedArray, struct MyStruct, MyStruct - passed and returned by value/copy with automatic padding).

Working with C structs via FFI

To pass or return a C struct by value, describe its layout with { struct: [...] } — an array that lists each field's FFI type in declaration order. Struct values are passed as a TypedArray whose bytes match the C layout, and structs returned by value come back as a Uint8Array of the right length. Key points: Layout matches the C compiler (Deno pads struct fields the same way your C compiler does); for packed structs, pad explicitly with u8 fields. Field order is positional — the struct array is just types in declaration order with no field names on the JavaScript side. Returned structs are always Uint8Array; view through appropriate TypedArray or DataView to read fields.

FFI struct example with Point struct

Example of passing and returning C structs by value. C code: typedef struct { double x; double y; } Point; double distance(Point a, Point b) { double dx = a.x - b.x; double dy = a.y - b.y; return __builtin_sqrt(dx * dx + dy * dy); } Point midpoint(Point a, Point b) { Point m; m.x = (a.x + b.x) / 2.0; m.y = (a.y + b.y) / 2.0; return m; }. TypeScript usage: const Point = { struct: ["f64", "f64"] } as const; const lib = Deno.dlopen("./libpoint.so", { distance: { parameters: [Point, Point], result: "f64" }, midpoint: { parameters: [Point, Point], result: Point } } as const); const a = new Float64Array([1.0, 2.0]); const b = new Float64Array([4.0, 6.0]); const aBytes = new Uint8Array(a.buffer); const bBytes = new Uint8Array(b.buffer); console.log("distance =", lib.symbols.distance(aBytes, bBytes)); const midBytes = lib.symbols.midpoint(aBytes, bBytes); const mid = new Float64Array(midBytes.buffer); console.log("midpoint =", { x: mid[0], y: mid[1] }); lib.close();

FFI callbacks with Deno.UnsafeCallback

You can pass JavaScript functions as callbacks to native code using Deno.UnsafeCallback. Example: const callback = new Deno.UnsafeCallback({ parameters: ["i32"], result: "void" } as const, (value) => { console.log("Callback received:", value); }); dylib.symbols.setCallback(callback.pointer); dylib.symbols.runCallback(); callback.close();

FFI best practices

Best practices when working with FFI: 1) Always close resources with dylib.close() for libraries and callback.close() for callbacks when done. 2) Prefer TypeScript for better type-checking when working with FFI. 3) Wrap FFI calls in try/catch blocks to handle errors gracefully. 4) Be extremely careful when using FFI, as native code can bypass Deno's security sandbox. 5) Keep the FFI interface as small as possible to reduce the attack surface.

Rust library FFI example with fibonacci

Example of creating and using a Rust library with Deno. Rust library (lib.rs): #[unsafe(no_mangle)] pub extern "C" fn fibonacci(n: u32) -> u32 { if n <= 1 { return n; } fibonacci(n - 1) + fibonacci(n - 2) }. Compile as dynamic library: rustc --crate-type cdylib lib.rs. TypeScript usage: const libName = { windows: "./lib.dll", linux: "./liblib.so", darwin: "./liblib.dylib" }[Deno.build.os]; const dylib = Deno.dlopen(libName, { fibonacci: { parameters: ["u32"], result: "u32" } } as const); const result = dylib.symbols.fibonacci(10); console.log(`Fibonacci(10) = ${result}`); dylib.close();

Node-API support as alternative to FFI

Deno supports Node-API (N-API) for compatibility with native Node.js addons. This enables reusing existing native modules written for Node.js. Can load Node-API addon directly: import process from "node:process"; process.dlopen(module, "./native_module.node", 0). Or use npm packages with Node-API addons: import someNativeAddon from "npm:some-native-addon"; console.log(someNativeAddon.doSomething()). Differences from FFI: FFI has no build step required and is tied to library ABI; Node-API requires precompiled binaries or build step but is ABI-stable across versions. FFI is for direct library calls; Node-API is for reusing Node.js addons.

Alternatives to FFI for native code integration

Before using FFI, consider these alternatives: WebAssembly for portable native code that runs within Deno's sandbox. Use Deno.command() to execute external binaries and subprocesses with controlled permissions. Check whether Deno's native APIs already provide the functionality you need.

Node-API native addons support in Deno

Deno supports Node-API addons used by packages like esbuild, sqlite3, and duckdb when a local node_modules/ directory is present. Configure "nodeModulesDir": "auto" | "manual" in deno.json or run with --node-modules-dir=auto|manual. Pass --allow-ffi to grant explicit permission for FFI access.

FFI Deno.dlopen to call native libraries

Deno.dlopen(libName, symbols) opens a dynamic library and defines exported symbols. The symbols object maps symbol names to definitions with parameters, result, and optional nonblocking fields. Example: const dylib = Deno.dlopen("./libadd.so", { "add": { parameters: ["isize", "isize"], result: "isize" } } as const); const result = dylib.symbols.add(35, 34); Returns 69. Run with --allow-ffi and --unstable flags.

FFI nonblocking functions return Promise

In Deno.dlopen, symbols can be marked nonblocking: true, which causes function calls to run on a dedicated blocking thread and return a Promise resolving to the desired result. Example: sleep: { parameters: ["usize"], result: "void", nonblocking: true } allows library.symbols.sleep(500).then(() => console.log("After")); to execute without blocking.

Deno.UnsafeCallback for FFI callbacks

Create C callbacks from JavaScript functions using new Deno.UnsafeCallback({ parameters: [...], result: "..." } as const, (args) => {}). If the callback function throws an error, it propagates to the function that triggered the callback. If a callback returning a value throws, Deno returns 0 (null pointer for pointers) as the result. UnsafeCallback is not deallocated by default; call callback.close() to properly dispose of it after use.

FFI supported types mapping table

FFI types map as follows: i8 (number / char / i8), u8 (number / unsigned char / u8), i16 (number / short int / i16), u16 (number / unsigned short int / u16), i32 (number / int / i32), u32 (number / unsigned int / u32), i64 (bigint / long long int / i64), u64 (bigint / unsigned long long int / u64), usize (bigint / size_t / usize), isize (bigint / size_t / isize), f32 (number / float / f32), f64 (number / double / f64), void (undefined / void / ()) - void only for result, pointer ({} | null / void * / *mut c_void), buffer (TypedArray | null / uint8_t * / *mut u8), function ({} | null / void (*fun)() / Option<extern "C" fn()>), struct (TypedArray / struct MyStruct / MyStruct). As of Deno 1.31, pointer type JavaScript representation is an opaque pointer object or null for null pointers.

Give your agent this brain