This document is for maintainers and contributors to Bun, and describes internal implementation details.
*.bind.ts files to find function and class
definitions, and generates glue code to interop between JavaScript and native
code.
There are currently other code generators and systems that achieve similar
purposes. The following will all eventually be phased out in favor of this one:
- “Classes generator”, converting
*.classes.tsfor custom classes. - “JS2Native”, allowing ad-hoc calls from
src/jsto native code.
Creating JS Functions in Rust
Given a file implementing a simple function, such asadd:
src/jsc/bindgen_test.rs
.bind.ts file. The binding file goes
next to the Rust file.
crate::r#gen::<basename> (for bindgen_test.bind.ts,
that’s crate::r#gen::bindgen_test). To construct a JSFunction wrapping the
native implementation, use generated::create_add_callback(global):
src/js/ may use $bindgenFn("bindgen_test.bind.ts", "add") to
get a handle to the implementation.
Exported bindgen functions are snake_cased on the Rust side
(requiredAndOptionalArg → required_and_optional_arg), and the generated
callback constructor follows the same convention
(create_required_and_optional_arg_callback).
Strings
The type for receiving strings is one oft.DOMString, t.ByteString, and t.USVString. These map directly to their WebIDL counterparts and have slightly different conversion logic. Bindgen passes bun_core::String to native code in all cases.
When in doubt, use DOMString.
t.UTF8String can be used in place of t.DOMString, but will eagerly convert
to UTF-8. The native callback receives a &[u8] slice (WTF-8 data) that is
freed after the function returns.
TLDRs from the WebIDL spec:
- ByteString can only contain valid latin1 characters. It is not safe to assume
bun_core::Stringis already in 8-bit format, but it is extremely likely. - USVString will not contain invalid surrogate pairs, i.e. text that can be represented correctly in UTF-8.
- DOMString is the loosest but also the most recommended strategy.
Function Variants
Avariants can specify multiple variants (also known as overloads).
t.dictionary
A dictionary is a definition for a JavaScript object, typically as a function input. For function outputs, it is usually smarter to declare a class type so you can add methods and support destructuring.
Enumerations
To use WebIDL’s enumeration type, uset.stringEnum to create and codegen a new enum type.
An example of stringEnum as used in fmt_jsc.bind.ts / bun:internal-for-testing:
#[repr(u8)] enum. Note that
bindgen sorts t.stringEnum values alphabetically before emitting the C++
enum class, so discriminants must match the generated header’s order, not
the .bind.ts declaration order:
implNamespace
Setting implNamespace: "foo" on a fn({...}) routes the generated call to
crate::<basename>::foo::fn_name instead of crate::<basename>::fn_name. Use
this to group related binding implementations under a submodule.
t.oneOf
A oneOf is a union between two or more types. It is represented as a Rust
enum with one variant per member type.
Attributes
There is a set of attributes that can be chained ontot.* types. On all types:
.required, in dictionary parameters only.optional, in function arguments only.default(T)
.optional, it is lowered to a Rust Option<T>:
Integer Attributes
Integer types allow customizing overflow behavior withclamp or enforceRange:
validateInteger, validateNumber,
and more are available. Use these when implementing Node.js APIs so the error
messages match Node exactly.
Unlike enforceRange, which is taken from WebIDL, the validate* functions
are much stricter on the input they accept. For example, Node’s numerical
validator checks typeof value === 'number', while WebIDL uses ToNumber for
lossy conversion.