Skip to main content
bun:ffi has experimental support for compiling and running C from JavaScript with low overhead.

Usage (cc in bun:ffi)

See the introduction blog post for more information. JavaScript:
hello.js
import {cc} from 'bun:ffi';
import source from './hello.c' with {type: 'file'};

const {
	symbols: {hello},
} = cc({
	source,
	symbols: {
		hello: {
			args: [],
			returns: 'int',
		},
	},
});

console.log('What is the answer to the universe?', hello());
C source:
hello.c
int hello() {
  return 42;
}
When you run hello.js, it will print:
terminal
bun hello.js
What is the answer to the universe? 42
Under the hood, cc uses TinyCC to compile the C code and then link it with the JavaScript runtime, efficiently converting types in-place.

Primitive types

The same FFIType values in dlopen are supported in cc.
FFITypeC TypeAliases
cstringchar*
function(void*)(*)()fn, callback
ptrvoid*pointer, void*, char*
i8int8_tint8_t
i16int16_tint16_t
i32int32_tint32_t, int
i64int64_tint64_t
i64_fastint64_t
u8uint8_tuint8_t
u16uint16_tuint16_t
u32uint32_tuint32_t
u64uint64_tuint64_t
u64_fastuint64_t
f32floatfloat
f64doubledouble
boolbool
charchar
napi_envnapi_env
napi_valuenapi_value

Strings, objects, and non-primitive types

To make it easier to work with strings, objects, and other non-primitive types that don’t map 1:1 to C types, cc supports N-API. To pass or receive a JavaScript values without any type conversions from a C function, you can use napi_value. You can also pass a napi_env to receive the N-API environment used to call the JavaScript function.

Returning a C string to JavaScript

For example, if you have a string in C, you can return it to JavaScript like this:
hello.js
import {cc} from 'bun:ffi';
import source from './hello.c' with {type: 'file'};

const {
	symbols: {hello},
} = cc({
	source,
	symbols: {
		hello: {
			args: ['napi_env'],
			returns: 'napi_value',
		},
	},
});

const result = hello();
And in C:
hello.c
#include <node/node_api.h>

napi_value hello(napi_env env) {
  napi_value result;
  napi_create_string_utf8(env, "Hello, Napi!", NAPI_AUTO_LENGTH, &result);
  return result;
}
You can also use this to return other types like objects and arrays:
hello.c
#include <node/node_api.h>

napi_value hello(napi_env env) {
  napi_value result;
  napi_create_object(env, &result);
  return result;
}

cc Reference

library: string[]

The library array is used to specify the libraries that should be linked with the C code.
type Library = string[];

cc({
	source: 'hello.c',
	library: ['sqlite3'],
});

symbols

The symbols object is used to specify the functions and variables that should be exposed to JavaScript.
type Symbols = {
	[key: string]: {
		args: FFIType[];
		returns: FFIType;
	};
};

source

The source is a file path to the C code that should be compiled and linked with the JavaScript runtime.
type Source = string | URL | BunFile;

cc({
	source: 'hello.c',
	symbols: {
		hello: {
			args: [],
			returns: 'int',
		},
	},
});

flags: string | string[]

The flags is an optional array of strings that should be passed to the TinyCC compiler.
type Flags = string | string[];
These are flags like -I for include directories and -D for preprocessor definitions.

define: Record<string, string>

The define is an optional object that should be passed to the TinyCC compiler.
type Defines = Record<string, string>;

cc({
	source: 'hello.c',
	define: {
		NDEBUG: '1',
	},
});
These are preprocessor definitions passed to the TinyCC compiler.
I