Foreign Function Interface API

As of Deno 1.13 and later, the FFI (foreign function interface) API allows users to call libraries written in native languages that support the C ABIs (Rust, C/C++, C#, Zig, Nim, Kotlin, etc) using Deno.dlopen.

Usage

Here’s an example showing how to call a Rust function from Deno:

  1. // add.rs
  2. #[no_mangle]
  3. pub extern "C" fn add(a: isize, b: isize) -> isize {
  4. a + b
  5. }

Compile it to a C dynamic library (libadd.so on Linux):

  1. rustc --crate-type cdylib add.rs

In C you can write it as:

  1. // add.c
  2. int add(int a, int b) {
  3. return a + b;
  4. }

And compile it:

  1. // unix
  2. cc -c -o add.o add.c
  3. cc -shared -W -o libadd.so add.o
  4. // Windows
  5. cl /LD add.c /link /EXPORT:libadd

Calling the library from Deno:

  1. // ffi.ts
  2. // Determine library extension based on
  3. // your OS.
  4. let libSuffix = "";
  5. switch (Deno.build.os) {
  6. case "windows":
  7. libSuffix = "dll";
  8. break;
  9. case "darwin":
  10. libSuffix = "dylib";
  11. break;
  12. default:
  13. libSuffix = "so";
  14. break;
  15. }
  16. const libName = `./libadd.${libSuffix}`;
  17. // Open library and define exported symbols
  18. const dylib = Deno.dlopen(libName, {
  19. "add": { parameters: ["isize", "isize"], result: "isize" },
  20. });
  21. // Call the symbol `add`
  22. const result = dylib.symbols.add(35, 34); // 69
  23. console.log(`Result from external addition of 35 and 34: ${result}`);

Run with --allow-ffi and --unstable flag:

  1. deno run --allow-ffi --unstable ffi.ts

Non-blocking FFI

There are many use cases where users might want to run CPU-bound FFI functions in the background without blocking other tasks on the main thread.

As of Deno 1.15, symbols can be marked nonblocking in Deno.dlopen. These function calls will run on a dedicated blocking thread and will return a Promise resolving to the desired result.

Example of executing expensive FFI calls with Deno:

  1. // sleep.c
  2. #ifdef _WIN32
  3. #include <Windows.h>
  4. #else
  5. #include <time.h>
  6. #endif
  7. int sleep(unsigned int ms) {
  8. #ifdef _WIN32
  9. Sleep(ms);
  10. #else
  11. struct timespec ts;
  12. ts.tv_sec = ms / 1000;
  13. ts.tv_nsec = (ms % 1000) * 1000000;
  14. nanosleep(&ts, NULL);
  15. #endif
  16. }

Calling it from Deno:

  1. // nonblocking_ffi.ts
  2. const library = Deno.dlopen("./sleep.so", {
  3. sleep: {
  4. parameters: ["usize"],
  5. result: "void",
  6. nonblocking: true,
  7. },
  8. });
  9. library.symbols.sleep(500).then(() => console.log("After"));
  10. console.log("Before");

Result:

  1. $ deno run --allow-ffi --unstable unblocking_ffi.ts
  2. Before
  3. After

Supported types

Here’s a list of types supported currently by the Deno FFI API.

FFI Type C Rust
i8 char / signed char i8
u8 unsigned char u8
i16 short int i16
u16 unsigned short int u16
i32 int / signed int i32
u32 unsigned int u32
i64 long long int i64
u64 unsigned long long int u64
usize size_t usize
f32 float f32
f64 double f64
void void ()
pointer[1] const uint8_t * *const u8
  • [1] pointer type accepts both Typed Arrays and Deno.UnsafePointer as parameter, while it always returns the latter when used as result type.

deno_bindgen

deno_bindgen is an external tool to simplify glue code generation of Deno FFI libraries written in Rust.

It is similar to wasm-bindgen in the Rust WASM ecosystem.

Here’s an example showing its usage:

  1. // mul.rs
  2. use deno_bindgen::deno_bindgen;
  3. #[deno_bindgen]
  4. struct Input {
  5. a: i32,
  6. b: i32,
  7. }
  8. #[deno_bindgen]
  9. fn mul(input: Input) -> i32 {
  10. input.a * input.b
  11. }

Run deno_bindgen to generate bindings. You can now directly import them into Deno:

ts, ignore // mul.ts import { mul } from "./bindings/bindings.ts"; mul({ a: 10, b: 2 }); // 20

Any issues related to deno_bindgen should be reported at https://github.com/littledivy/deno_bindgen/issues